C# learning and getting in to a right pickle ..

Soldato
Joined
8 Mar 2005
Posts
3,609
Location
London, UK
So I have been mucking about with the CU tutorial and am now attempting to mimic the CourseAssignment step using a dB generated primary key for the CourseID value instead of user defined one.

So in my example,
Student=Applications
Course=IndApp (individual applications)
CourseAssignment=AppAssignments

In effect; Applications is the solution name, where IndApp contains separate vendor applications presented to the solution.

However; when I attempt to seed the database it throws a wobbly. The error may appear pretty self-explanatory but I'm at a loss where to start.

As far as I can fathom; the AppAssignments table is created correctly and my classes are set correctly. Any pointers would be most welcome.


Code:
SELECT [t].[ID] FROM [Application] t
INNER JOIN @inserted0 i ON ([t].[ID] = [i].[ID])
ORDER BY [i].[_Position];
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (8ms) [Parameters=[@p0='?' (Size = 50), @p1='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [IndApp] ([Application], [Version])
VALUES (@p0, @p1);
SELECT [ID]
FROM [IndApp]
WHERE @@ROWCOUNT = 1 AND [ID] = scope_identity();
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@p0='?' (Size = 50), @p1='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [IndApp] ([Application], [Version])
VALUES (@p0, @p1);
SELECT [ID]
FROM [IndApp]
WHERE @@ROWCOUNT = 1 AND [ID] = scope_identity();
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (0ms) [Parameters=[@p0='?' (Size = 50), @p1='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [IndApp] ([Application], [Version])
VALUES (@p0, @p1);
SELECT [ID]
FROM [IndApp]
WHERE @@ROWCOUNT = 1 AND [ID] = scope_identity();
Exception thrown: 'System.InvalidOperationException' in Microsoft.EntityFrameworkCore.dll
'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.3\System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.3\System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.3\System.Text.Encoding.Extensions.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Tracker.Program:Error: An error occurred while seeding the database.

System.InvalidOperationException: The instance of entity type 'AppAssignment' cannot be tracked because another instance with the same key value for {'IndAppID', 'ApplicationID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
.......
Code:
CREATE TABLE [dbo].[AppAssignment] (
    [ApplicationID] INT NOT NULL,
    [IndAppID]      INT NOT NULL,
    CONSTRAINT [PK_AppAssignment] PRIMARY KEY CLUSTERED ([IndAppID] ASC, [ApplicationID] ASC),
    CONSTRAINT [FK_AppAssignment_Application_ApplicationID] FOREIGN KEY ([ApplicationID]) REFERENCES [dbo].[Application] ([ID]) ON DELETE CASCADE,
    CONSTRAINT [FK_AppAssignment_IndApp_IndAppID] FOREIGN KEY ([IndAppID]) REFERENCES [dbo].[IndApp] ([ID]) ON DELETE CASCADE
);


GO
CREATE NONCLUSTERED INDEX [IX_AppAssignment_ApplicationID]
    ON [dbo].[AppAssignment]([ApplicationID] ASC);
Code:
using Tracker.Models;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Tracker.Data
{
    public static class DbInitializer
    {
        public static void Initialize(TrackerContext context)
        {
            //context.Database.EnsureCreated();

            // Look for any students.
            if (context.Applications.Any())
            {
                return;   // DB has been seeded
            }

            var Applications = new Application[]
            {
            new Application{CTXApp="FirstApp",FirstMidName="Carson",LastName="Alexander",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Application{CTXApp="GCMS",FirstMidName="Meredith",LastName="Alonso",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Application{CTXApp="Oracle",FirstMidName="Arturo",LastName="Anand",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Application{CTXApp="Empower",FirstMidName="Gytis",LastName="Barzdukas",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Application{CTXApp="TCSDesktop",FirstMidName="Yan",LastName="Li",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Application{CTXApp="Documentum",FirstMidName="Peggy",LastName="Justice",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Application{CTXApp="OneCDS",FirstMidName="Laura",LastName="Norman",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Application{CTXApp="Access",FirstMidName="Nino",LastName="Olivetto",BusinessUnit="GMS",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Application a in Applications)
            {
                context.Applications.Add(a);
            }
            context.SaveChanges();

            /*var IndApps = new IndApp[]
            {
            new IndApp{IndAppID=1050,Title="Chemistry",Credits=3},
            new IndApp{IndAppID=4022,Title="Microeconomics",Credits=3},
            new IndApp{IndAppID=4041,Title="Macroeconomics",Credits=3},
            new IndApp{IndAppID=1045,Title="Calculus",Credits=4},
            new IndApp{IndAppID=3141,Title="Trigonometry",Credits=4},
            new IndApp{IndAppID=2021,Title="Composition",Credits=3},
            new IndApp{IndAppID=2042,Title="Literature",Credits=4}
            };
            foreach (IndApp ia in IndApps)
            {
                context.IndApps.Add(ia);
            }
            context.SaveChanges();*/

            var IndApps = new IndApp[]
           {
            new IndApp{Application="Access",Version="1"},
            new IndApp{Application="Excel",Version="2"},
            new IndApp{Application="Word",Version="3"}
           };
            foreach (IndApp ia in IndApps)
            {
                context.IndApps.Add(ia);
            }
            context.SaveChanges();


            var AppAssignments = new AppAssignment[]
            {
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Access" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "FirstApp").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Excel" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "GCMS").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Excel" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "FirstApp").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "FirstApp").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "Oracle").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "Empower").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Excel" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "TCSDesktop").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "Documentum").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Access" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "OneCDS").ID
                    },
            new AppAssignment {
                    IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "Empower").ID
                    }
                    };

            foreach (AppAssignment aa in AppAssignments)
            {
                context.AppAssignments.Add(aa);
            }
            context.SaveChanges();
            /*
            var enrollments = new Enrollment[]
            {
            new Enrollment{ApplicationID=1,IndAppID=1050,Grade=Grade.A},
            new Enrollment{ApplicationID=1,IndAppID=4022,Grade=Grade.C},
            new Enrollment{ApplicationID=1,IndAppID=4041,Grade=Grade.B},
            new Enrollment{ApplicationID=2,IndAppID=1045,Grade=Grade.B},
            new Enrollment{ApplicationID=2,IndAppID=3141,Grade=Grade.F},
            new Enrollment{ApplicationID=2,IndAppID=2021,Grade=Grade.F},
            new Enrollment{ApplicationID=3,IndAppID=1050},
            new Enrollment{ApplicationID=4,IndAppID=1050},
            new Enrollment{ApplicationID=4,IndAppID=4022,Grade=Grade.F},
            new Enrollment{ApplicationID=5,IndAppID=4041,Grade=Grade.C},
            new Enrollment{ApplicationID=6,IndAppID=1045},
            new Enrollment{ApplicationID=7,IndAppID=3141,Grade=Grade.A}
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges(); */
        }
    }
}
Code:
using Tracker.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;

namespace Tracker.Data
{
    public class TrackerContext : DbContext
    {
        public TrackerContext(DbContextOptions<TrackerContext> options) : base(options)
        {
        }

        public DbSet<IndApp> IndApps { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Application> Applications { get; set; }
        public DbSet<AppAssignment> AppAssignments { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<IndApp>().ToTable("IndApp");
            //modelBuilder.Entity<IndApp>().ToTable("IndApp2");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Application>().ToTable("Application");
            modelBuilder.Entity<AppAssignment>().ToTable("AppAssignment");


            modelBuilder.Entity<AppAssignment>()
               .HasKey(c => new { c.IndAppID, c.ApplicationID });

        }
    }
}
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Tracker.Models
{
    public class Application
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Display(Name = "Citrix Application")]
        public string CTXApp { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [StringLength(50, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }
        [Required]
        [StringLength(3, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Display(Name = "Business Unit")]
        public string BusinessUnit { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
        public ICollection<AppAssignment> AppAssignments { get; set; }
    }
}
Code:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;

namespace Tracker.Models
{
    public class IndApp
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Display(Name = "Application")]
        public string Application { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 1)]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
        [Display(Name = "Version")]
        public string Version { get; set; }
        public ICollection<AppAssignment> AppAssignments { get; set; }
    }
}
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Tracker.Models
{
    public class AppAssignment
    {
        public int ApplicationID { get; set; }
        public int IndAppID { get; set; }
        public Application Application { get; set; }
        public IndApp IndApp { get; set; }
    }
}
Cheers, Paul.
 
Soldato
Joined
28 Oct 2006
Posts
12,456
Location
Sufferlandria
You've got you're table setup correctly, the primary key is a clustered key of both ApplicationID and IndAppID fields. That means you can have duplicates in either field but not in both fields at the same time.

So the issue is this section in DbInitializer.cs which is duplicated twice:
Code:
new AppAssignment {
                   IndAppID = IndApps.Single(ia => ia.Application == "Word" ).ID,
                    ApplicationID = Applications.Single(a => a.CTXApp == "Empower").ID
                    }
 
Soldato
OP
Joined
8 Mar 2005
Posts
3,609
Location
London, UK
So next hiccup.

As per this section I'm now trying to add/remove elements.

The solution builds successfully but will throw out the following error when editing an application.
Code:
ArgumentNullException: Value cannot be null. Parameter name: source

System.Linq.Enumerable.Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)


Tracker.Controllers.ApplicationsController.PopulateAssignedAppData(Application application) in ApplicationsController.cs
-
179.            return View(application);
180.        }
181.
182.        private void PopulateAssignedAppData(Application application)
183.        {
184.            var allindapps = _context.IndApps;
185.            var CTXAppsApps = new HashSet<int>(application.AppAssignments.Select(c => c.IndAppID));
186.            var viewModel = new List<AssignedIndAppData>();
187.            foreach (var indApp in allindapps)
188.            {
189.                viewModel.Add(new AssignedIndAppData
190.                {
191.                    IndAppID = indApp.ID,


Tracker.Controllers.ApplicationsController.Edit(Nullable<int> id) in ApplicationsController.cs
-
172.            if (application == null)
173.
174.
175.            {
176.                return NotFound();
177.            }
178.            PopulateAssignedAppData(application);
179.            return View(application);
180.        }
181.
182.        private void PopulateAssignedAppData(Application application)
183.        {
184.            var allindapps = _context.IndApps;


Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)


System.Threading.Tasks.ValueTask<TResult>.get_Result()


Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()


Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()


Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)


Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)


Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()


Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()


Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)


Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)


Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()


Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()


Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)


Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)


Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)


Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Tracker.Data;
using Tracker.Models;
using Tracker.Models.TrackerViewModels;

namespace Tracker.Controllers
{
    public class ApplicationsController : Controller
    {
        private readonly TrackerContext _context;

        public ApplicationsController(TrackerContext context)
        {
            _context = context;
        }

        // GET: Applications
        public async Task<IActionResult> Index(int? id, int? indappID,
            string sortOrder,
            string currentFilter,
            string searchString,
            int? pageNumber)
        {
            ViewData["CurrentSort"] = sortOrder;
            ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
            ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

            if (searchString != null)
            {
                pageNumber = 1;
            }
            else
            {
                searchString = currentFilter;
            }



            ViewData["CurrentFilter"] = searchString;

            var Applications = from a in _context.Applications
                               select a;

            if (!String.IsNullOrEmpty(searchString))
            {
                Applications = Applications.Where(s => s.LastName.Contains(searchString)
                                       || s.FirstMidName.Contains(searchString));
            }

            switch (sortOrder)
            {
                case "name_desc":
                    Applications = Applications.OrderByDescending(a => a.LastName);
                    break;
                case "Date":
                    Applications = Applications.OrderBy(a => a.EnrollmentDate);
                    break;
                case "date_desc":
                    Applications = Applications.OrderByDescending(a => a.EnrollmentDate);
                    break;
                default:
                    Applications = Applications.OrderBy(a => a.LastName);
                    break;
            }

            var viewModel = new ApplicationIndexData();
            viewModel.Applications = await _context.Applications
                  .Include(i => i.AppAssignments)
                    .ThenInclude(i => i.Application)
                  //      .ThenInclude(i => i.CTXApp)
                  .OrderBy(i => i.LastName)
                  .ToListAsync();

            if (id != null)
            {
                ViewData["ApplicationID"] = id.Value;
                Application application = viewModel.Applications.Where(
                    i => i.ID == id.Value).Single();
                viewModel.IndApps = application.AppAssignments.Select(s => s.IndApp);
                  
            }

            if (indappID != null)
            {
                ViewData["IndAppID"] = indappID.Value;
                //    viewModel.Enrollments = viewModel.Courses.Where(
               // x => x.IndAppID == indappID).Single().Enrollments;
            }


            int pageSize = 10;
            return View(await PaginatedList<Application>.CreateAsync(Applications.AsNoTracking(), pageNumber ?? 1, pageSize));
        }

        // GET: Applications/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var Application = await _context.Applications
                .Include(a => a.AppAssignments)
                    .ThenInclude(e => e.IndApp)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Application == null)
            {
                return NotFound();
            }

            return View(Application);
        }

        // GET: Applications/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: Applications/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create(
            [Bind("EnrollmentDate,BusinessUnit,FirstMidName,LastName,CTXApp")] Application application)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    _context.Add(application);
                    await _context.SaveChangesAsync();
                    return RedirectToAction(nameof(Index));
                }
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.
                ModelState.AddModelError("", "Unable to save changes. " +
                    "Try again, and if the problem persists " +
                    "see your system administrator.");
            }
            return View(application);
        }

        // GET: Applications/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var application = await _context.Applications.FindAsync(id);

            var instructor = await _context.Applications
               // .Include(i => i.OfficeAssignment)
               .Include(i => i.AppAssignments).ThenInclude(i => i.IndApp)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (application == null)


            {
                return NotFound();
            }
            PopulateAssignedAppData(application);
            return View(application);
        }

        private void PopulateAssignedAppData(Application application)
        {
            var allindapps = _context.IndApps;
            var CTXAppsApps = new HashSet<int>(application.AppAssignments.Select(c => c.IndAppID));
            var viewModel = new List<AssignedIndAppData>();
            foreach (var indApp in allindapps)
            {
                viewModel.Add(new AssignedIndAppData
                {
                    IndAppID = indApp.ID,
                    AppName = indApp.Application    ,
                    Assigned = CTXAppsApps.Contains(indApp.ID)
                });
            }
            ViewData["IndApps"] = viewModel;
        }



        // POST: Applications/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        // [HttpPost, ActionName("Edit")]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int? id, string[] selectedIndApps)
        {
            if (id == null)
            {
                return NotFound();
            }

            //var ApplicationToUpdate = await _context.Applications.FirstOrDefaultAsync(a => a.ID == id);

            var ApplicationToUpdate = await _context.Applications
               //    .Include(i => i.OfficeAssignment)
                   .Include(i => i.AppAssignments)
                    .ThenInclude(i => i.IndApp)
                    .FirstOrDefaultAsync(m => m.ID == id);



            if (await TryUpdateModelAsync<Application>(
                ApplicationToUpdate,
                "",
                a => a.BusinessUnit,a => a.FirstMidName, a => a.LastName, a => a.EnrollmentDate))
            {

                UpdateApplicationIndApps(selectedIndApps, ApplicationToUpdate);

                try
                {
                    await _context.SaveChangesAsync();
                    return RedirectToAction(nameof(Index));
                }
                catch (DbUpdateException /* ex */)
                {
                    //Log the error (uncomment ex variable name and write a log.)
                    ModelState.AddModelError("", "Unable to save changes. " +
                        "Try again, and if the problem persists, " +
                        "see your system administrator.");
                }
            }

            UpdateApplicationIndApps(selectedIndApps, ApplicationToUpdate);
            PopulateAssignedAppData(ApplicationToUpdate);

            return View(ApplicationToUpdate);
        }


        private void UpdateApplicationIndApps(string[] selectedIndApps, Application ApplicationToUpdate)
        {
            if (selectedIndApps == null)
            {
                ApplicationToUpdate.AppAssignments = new List<AppAssignment>();
                return;
            }

            //var selectedIndAppsHS = new HashSet<string>(selectedIndApps);
            var selectedIndAppsHS = new HashSet<string>(selectedIndApps);
            var ApplicationIndApps = new HashSet<int>
                (ApplicationToUpdate.AppAssignments.Select(c => c.IndApp.ID));
            foreach (var indapp in _context.Applications)
            {
                if (selectedIndAppsHS.Contains(indapp.ID.ToString()))
                {
                    if (!ApplicationIndApps.Contains(indapp.ID))
                    {
                        ApplicationToUpdate.AppAssignments.Add(new AppAssignment { ApplicationID = ApplicationToUpdate.ID, IndAppID = indapp.ID });
                    }
                }
                else
                {

                    if (ApplicationIndApps.Contains(indapp.ID))
                    {
                        AppAssignment IndAppToRemove = ApplicationToUpdate.AppAssignments.FirstOrDefault(i => i.IndAppID == indapp.ID);
                        _context.Remove(IndAppToRemove);
                    }
                }
            }
        }











        // GET: Applications/Delete/5
        public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            var Application = await _context.Applications
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);
            if (Application == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ViewData["ErrorMessage"] =
                    "Delete failed. Try again, and if the problem persists " +
                    "see your system administrator.";
            }

            return View(Application);
        }

        // POST: Applications/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var application = await _context.Applications.FindAsync(id);

            if (application == null)
            {
                return RedirectToAction(nameof(Index));
            }

            try
            {

                _context.Applications.Remove(application);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
            }
        }

        private bool ApplicationExists(int id)
        {
            return _context.Applications.Any(e => e.ID == id);
        }
    }
}
Code:
@model Tracker.Models.Application

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Application</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="ID" />
            <div class="form-group">
                <label asp-for="CTXApp" class="control-label"></label>
                <input asp-for="CTXApp" class="form-control" />
                <span asp-validation-for="CTXApp" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="LastName" class="control-label"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FirstMidName" class="control-label"></label>
                <input asp-for="FirstMidName" class="form-control" />
                <span asp-validation-for="FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="BusinessUnit" class="control-label"></label>
                <input asp-for="BusinessUnit" class="form-control" />
                <span asp-validation-for="BusinessUnit" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="EnrollmentDate" class="control-label"></label>
                <input asp-for="EnrollmentDate" class="form-control" />
                <span asp-validation-for="EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;
                                List<Tracker.Models.TrackerViewModels.AssignedIndAppData> indapps = ViewBag.Indapps;

                                foreach (var indapp in indapps)
                                {
                                    if (cnt++ % 3 == 0)
                                    {
                                    @:</tr><tr>
                                    }
                                    @:<td>
                                        <input type="checkbox"
                                               name="selectedCourses"
                                               value="@indapp.IndAppID"
                                               @(Html.Raw(indapp.Assigned ? "checked=\"checked\"" : "")) />
                                        @indapp.IndAppID @:  @indapp.AppName
                                    @:</td>
                                }
                            @:</tr>
                            }
                        </table>
                    </div>
                </div>
                <div class="form-group">
                    <input type="submit" value="Save" class="btn btn-primary" />
                </div>
            </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Any pointers; much appreciated.
Cheers, Paul.
 
Associate
Joined
19 Aug 2016
Posts
477
Location
Bristol
The exception means that whatever you are calling Select() on is null. I believe you can prevent the exception by instead calling Collection?.Select() - The ?. operator will return null if the collection is null, rather than proceeding to call the Select() method.

However that only stops the exception, doesn't really help with the underlying issue of why the collection is null ;)
 
Soldato
Joined
28 Oct 2006
Posts
12,456
Location
Sufferlandria
HashSet<int>(application.AppAssignments.Select(c => c.IndAppID));

This line gets all of the AppAssignments which belong to application and then output the IndAppID value into your hashset.
That's all going to work fine provided you have at least 1 AppAssignment or (as you've seen from the error you're getting) it's going to crash if there arn't any.
The problem is that you're not just selecting all the AppAssignments from the AppAssignment table (you'd do _context.AppAssignments for that), you're using application.AppAssignments to get all the AppAssignments linked to the application object which you passed in. If the application object you've passed in didn't have any linked AppAssignments, that'll cause the error you're getting. Looking at the db table you posted earlier, applications with IDs between 1 and 8 don't have any linked AppAssignments
 
Soldato
OP
Joined
8 Mar 2005
Posts
3,609
Location
London, UK
Thank you all for the replies.
The exception means that whatever you are calling Select() on is null. I believe you can prevent the exception by instead calling Collection?.Select() - The ?. operator will return null if the collection is null, rather than proceeding to call the Select() method.

However that only stops the exception, doesn't really help with the underlying issue of why the collection is null ;)
I understand this simply masks the exception. I did look for examples for this but could not find the syntax Collection?.Select(), only collection_select().

Could you use the new Live Share in VS2017/19 or VS Code? Sure someone could just jump in and help out that way.
I'll have a look at thanks.
HashSet<int>(application.AppAssignments.Select(c => c.IndAppID));

This line gets all of the AppAssignments which belong to application and then output the IndAppID value into your hashset.
That's all going to work fine provided you have at least 1 AppAssignment or (as you've seen from the error you're getting) it's going to crash if there arn't any.
The problem is that you're not just selecting all the AppAssignments from the AppAssignment table (you'd do _context.AppAssignments for that), you're using application.AppAssignments to get all the AppAssignments linked to the application object which you passed in. If the application object you've passed in didn't have any linked AppAssignments, that'll cause the error you're getting. Looking at the db table you posted earlier, applications with IDs between 1 and 8 don't have any linked AppAssignments
OK this makes sense. However, in this instance and following on from the CU tutorial; I would have expected it would allow for instances where the Application may not be associated with any IndApp (individual application) and visa versa.

I did recheck the table data and in this example the error still occurs when there is at least one IndApp associated with the Application.

Code:
ID    Application    Version
4    Access    1
5    Excel    2
6    Word    3
Code:
ID    LastName    FirstName    BusinessUnit    EnrollmentDate    CTXApp
9    Alexander    Carson    GMS    2005-09-01 00:00:00.0000000    FirstApp
10    Alonso    Meredith    GMS    2002-09-01 00:00:00.0000000    GCMS
11    Anand    Arturo    GMS    2003-09-01 00:00:00.0000000    Oracle
12    Barzdukas    Gytis    GMS    2002-09-01 00:00:00.0000000    Empower
13    Li    Yan    GMS    2002-09-01 00:00:00.0000000    TCSDesktop
14    Justice    Peggy    VAX    2001-09-01 00:00:00.0000000    Documentum
15    Norman    Laura    GMS    2003-09-01 00:00:00.0000000    OneCDS
Code:
ApplicationID    IndAppID
9    4
9    5
9    6
10    5
11    6
12    6
13    5
14    6
15    4

--
EDIT. Removed the exception.

var application = await _context.Applications.FindAsync(id);
>
var application = await _context.Applications

Now on to the next challenge :) I am able to check and uncheck individual applications when editing the Applications but when saving it looks it does not make the insert/delete calls and just returns back to the Application list. No errors that I can see.
Code:
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            //var application = await _context.Applications.FindAsync(id);

            var application = await _context.Applications
               // .Include(i => i.OfficeAssignment)
               .Include(i => i.AppAssignments).ThenInclude(i => i.IndApp)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (application == null)


            {
                return NotFound();
            }
            PopulateAssignedAppData(application);
            return View(application);
        }

        private void PopulateAssignedAppData(Application application)
        {
            var allindapps = _context.IndApps;
            var CTXAppsApps = new HashSet<int>(application.AppAssignments.Select(c => c.IndAppID));
            var viewModel = new List<AssignedIndAppData>();
            foreach (var indApp in allindapps)
            {
                viewModel.Add(new AssignedIndAppData
                {
                    IndAppID = indApp.ID,
                    AppName = indApp.Application    ,
                    Assigned = CTXAppsApps.Contains(indApp.ID)
                });
            }
            ViewData["IndApps"] = viewModel;
        }



        // POST: Applications/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        // [HttpPost, ActionName("Edit")]
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int? id, string[] selectedIndApps)
        {
            if (id == null)
            {
                return NotFound();
            }

            //var ApplicationToUpdate = await _context.Applications.FirstOrDefaultAsync(a => a.ID == id);

            var ApplicationToUpdate = await _context.Applications
                    .Include(i => i.AppAssignments)
                    .ThenInclude(i => i.IndApp)
                    .FirstOrDefaultAsync(m => m.ID == id);

            if (await TryUpdateModelAsync<Application>(
                ApplicationToUpdate,
                "",
                i => i.CTXApp, i => i.BusinessUnit,i => i.FirstMidName, i => i.LastName, i => i.EnrollmentDate))
            {
                UpdateApplicationIndApps(selectedIndApps, ApplicationToUpdate);
                try
                {
                    await _context.SaveChangesAsync();
                  
                }
                catch (DbUpdateException /* ex */)
                {
                    //Log the error (uncomment ex variable name and write a log.)
                    ModelState.AddModelError("", "Unable to save changes. " +
                        "Try again, and if the problem persists, " +
                        "see your system administrator.");
                }
                return RedirectToAction(nameof(Index));
            }

            UpdateApplicationIndApps(selectedIndApps, ApplicationToUpdate);
            PopulateAssignedAppData(ApplicationToUpdate);
            return View(ApplicationToUpdate);
        }


        private void UpdateApplicationIndApps(string[] selectedIndApps, Application ApplicationToUpdate)
        {
            if (selectedIndApps == null)
            {
                ApplicationToUpdate.AppAssignments = new List<AppAssignment>();
                return;
            }

            var selectedIndAppsHS = new HashSet<string>(selectedIndApps);
            var ApplicationIndApps = new HashSet<int>
                (ApplicationToUpdate.AppAssignments.Select(c => c.IndApp.ID));
            foreach (var indapp in _context.Applications)
            {
                if (selectedIndAppsHS.Contains(indapp.ID.ToString()))
                {
                    if (!ApplicationIndApps.Contains(indapp.ID))
                    {
                        ApplicationToUpdate.AppAssignments.Add(new AppAssignment { ApplicationID = ApplicationToUpdate.ID, IndAppID = indapp.ID });
                    }
                }
                else
                {

                    if (ApplicationIndApps.Contains(indapp.ID))
                    {
                        AppAssignment IndAppToRemove = ApplicationToUpdate.AppAssignments.FirstOrDefault(i => i.IndAppID == indapp.ID);
                        _context.Remove(IndAppToRemove);
                    }
                }
            }
        }


---
Second issue relates to use paginated lists. index.cshtml is throwing an error suggesting missing directive or Assembly reference.

Throwing the error at lines;
@foreach (var item in Model.Applications)

@if (Model.IndApps != null)

@foreach (var item in Model.IndApps)

Yet; These are included in the referenced class on the first line of the index.cshtml
@model PaginatedList<Tracker.Models.TrackerViewModels.ApplicationIndexData>
Code:
@model PaginatedList<Tracker.Models.TrackerViewModels.ApplicationIndexData>


@{
    ViewData["Title"] = "Citrix Applicatiom";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData[" currentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">CTXApp</a>
            </th>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
            </th>
            <th>
                First Name
            </th>
            <th>
                Business Unit
            </th>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData[" DateSortParm"]" asp-route-currentFilter="@ViewData[" CurrentFilter"]">Enrollment Date</a>
            </th>
            <th>
                Application
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Applications)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["ApplicationID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.DisplayFor(modelItem => item.CTXApp)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.BusinessUnit)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.EnrollmentDate)
                </td>
                <td>
                    @{
                        foreach (var indapp in item.AppAssignments)
                        {
                            @indapp.IndApp.ID @:  @indapp.IndApp.Application <br />
                        }
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>


@if (Model.IndApps != null)
{
    <h3>Applications presented to solution.</h3>
    <table class="table">
        <tr>
            <th></th>
            <th>ID</th>
            <th>Application</th>
            <th>Version</th>
        </tr>

        @foreach (var item in Model.IndApps)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["CourseID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", new { indappID = item.ID })
                </td>
                <td>
                    @item.ID
                </td>
                <td>
                    @item.Application
                </td>
                <td>
                    @item.Version
                </td>
            </tr>
        }

    </table>
}

    @{
        var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
        var nextDisabled = !Model.HasNextPage ? "disabled" : "";
    }

    <a asp-action="Index"
       asp-route-sortOrder="@ViewData[" CurrentSort"]"
       asp-route-pageNumber="@(Model.PageIndex - 1)"
       asp-route-currentFilter="@ViewData[" CurrentFilter"]"
       class="btn btn-default @prevDisabled">
        Previous
    </a>
    <a asp-action="Index"
       asp-route-sortOrder="@ViewData[" CurrentSort"]"
       asp-route-pageNumber="@(Model.PageIndex + 1)"
       asp-route-currentFilter="@ViewData[" CurrentFilter"]"
       class="btn btn-default @nextDisabled">
        Next
    </a>
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Tracker.Models.TrackerViewModels
{
    public class ApplicationIndexData
    {
        public IEnumerable<Application> Applications { get; set; }
        public IEnumerable<IndApp> IndApps { get; set; }
    }
}
 
Last edited:
Back
Top Bottom