Is this correct? comparing 2 lists with Except

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
LVL 8
CamilliaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Cloud Class® Course: Microsoft Office 2010

This course will introduce you to the interfaces and features of Microsoft Office 2010 Word, Excel, PowerPoint, Outlook, and Access. You will learn about the features that are shared between all products in the Office suite, as well as the new features that are product specific.

CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
I misunderstood. See attached screenshot please
code.png
0
ste5anSenior DeveloperCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
David Johnson, CD, MVPOwnerCommented:
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
ste5anSenior DeveloperCommented:
Code originally deletes the rows and re-adds them.

Sounds like a perfect task for a stored procedure.
0
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
creating a onedrive account. Let you know when I upload the code
0
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
ok, thanks
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
Got it. Let me see what I can figure out.
0
CamilliaAuthor Commented:
Thanks. I'm looking at BulkCopy...so still delete but use bulkcopy. It shouldn't be deleting to begin with.
0
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
I'm still testing this and taking some screenshots. Will post back soon.
0
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
David Johnson, CD, MVPOwnerCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
David Johnson, CD, MVPOwnerCommented:
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
CamilliaAuthor Commented:
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
David Johnson, CD, MVPOwnerCommented:
where do the objects come from?
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
Thank you so much, Fernando. Let me take a look and try it.
0
CamilliaAuthor Commented:
So, this code replaces that "foreach" loop?? which section from the orig code do I need to remove and replace with this?
0
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
Howcome I get the attached error? I even retyped it just in case.
error.png
0
Fernando SotoRetiredCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
CamilliaAuthor Commented:
Thanks, let me try.  I really appreciate all the help this weekend. That was beyond being nice of you.
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
. 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
CamilliaAuthor Commented:
(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
CamilliaAuthor Commented:
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
ste5anSenior DeveloperCommented:
Without testing: Your hash looks not perfect.
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
Are you changing items that InputAttributeEqualityComparer is NOT testing for equality on?
0
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
If you bring up the form and change two  values, Name and Value, does it update the list?
0
CamilliaAuthor Commented:
let me try it now.
0
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
Well then they are not being stored where you think they should be or they are being deleted somewhere else.
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
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
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
Thanks, Camilla. I wish I could have been more help. Good luck and have a great day.
0
CamilliaAuthor Commented:
You were more than a fantastic help. I wish I could repay you with something else besides 500 points. Cookies, maybe:)
0
Fernando SotoRetiredCommented:
Thank you.
0
CamilliaAuthor Commented:
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
Fernando SotoRetiredCommented:
Hi Netminder;

Skype was not used although it was suggested it was not used.
0
CamilliaAuthor Commented:
Netminder, as Fernando said, we didn't use Skype. I have a better understanding of the rules now. Thanks
0
CamilliaAuthor Commented:
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
CamilliaAuthor Commented:
Thanks, Netminder, for your clarification.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C#

From novice to tech pro — start learning today.