How to insert data in a joined table with Entity framework? - entity-framework

I am new to Entity Framework and I am hoping for some help to insert data in a "joined table".
I have three tables, Profiles, Tags and one called ProfilesTags that joins these two tables. Classes are autogenerated from database / DB First.
public partial class Profiles
{
public Profiles()
{
this.ProfilesTags = new HashSet<ProfilesTags>();
}
public int ProfileId { get; set; }
public string Name { get; set; }
...
public virtual ICollection<ProfilesTags> ProfilesTags { get; set; }
}
public partial class Tags
{
public Tags()
{
this.ProfilesTags = new HashSet<ProfilesTags>();
}
public int TagId { get; set; }
public string Tag { get; set; }
public virtual ICollection<ProfilesTags> ProfilesTags { get; set; }
}
public partial class ProfilesTags
{
public int Id { get; set; }
public int ProfileId { get; set; }
public int TagId { get; set; }
public virtual Tags Tags { get; set; }
public virtual Profiles Profiles { get; set; }
}
I have a SaveTags method that looks like this:
public void SaveTags(int profileId, IEnumerable<TagsNameValue> tags)
{
var pr = Context.Profiles.First(p => p.ProfileId == profileId);
// remove any existing
pr.ProfilesTags.Clear();
if (tags == null || !tags.Any())
return;
var ids = tags.Select(value => value.Value);
var names = tags.Select(value => value.Name);
// get a list of tags for lookup from [Tags]-table
var tagsList = Context.Tags.Where(t => ids.Any(v => t.TagId == v) || names.Any(v => t.Tag == v)).ToList();
foreach (var nameValue in tags)
{
var tag = tagsList.FirstOrDefault(t => t.TagId == nameValue.Value || t.Tag.ToLower() == nameValue.Name.ToLower());
// Tag is already in [Tags], no need to recreate id, just associate it.
if (tag != null)
{
var tagModel = new ProfilesTags()
{
TagId = nameValue.Value,
ProfileId = profileId
};
pr.ProfilesTags.Add(tagModel);
}
// create new item in [Tags] table first and add association [ProfilesTags]
else
{
var newTag = new Tags { Tag = nameValue.Name};
// how do I associate this newly added tag to pr.ProfilesTags ?
// what to do / how to procede?
Context.Tags.Add(newTag);
}
}
Context.SaveChanges()
}
How can I associate newTag with pr.ProfilesTags?

It seems newTag should have a valid id first, then build the relationship by ProfilesTags later.
// create new item in [Tags] table first and add association [ProfilesTags]
else
{
var newTag = new Tags { Tag = nameValue.Name};
// how do I associate this newly added tag to pr.ProfilesTags ?
// what to do / how to procede?
Context.Tags.Add(newTag);
// Let newTag has a valid tagId
Context.SaveChanges();
// Build the relationship between `Profiles` and `Tags`.
var newProfileTag = new ProfilesTags();
/// Build the relationship by ForeignKey,
/// Or, call pr.ProfilesTags.Add(newProfileTag)
newProfileTag.ProfiledId = pr.ProfileId;
newProfileTag.TagId = newTag.TagId; //< It SHOULD NOT be zero...
Context.ProfilesTags.Add(newProfileTag);
// Save the newProfileTag.....
// Context.SaveChanges();
}

Related

EF Core 3.0 Select Projection with index overload (aka .Select((entity, index) => new {}) fails

I have current setup, with a Select indexer-projection (entity, index) (see SubRubrics). If i leave the indexer out, the problem is solved... However if I leave out the SubRubricItems then I can use the indexer. Is it only on the last select projection I can use it, or..?
Below linq projection, error message and more info.
await _db
.Exams
.AsNoTracking()
.Include(exam => exam.Stations)
.ThenInclude(station => station.Rubrics)
.ThenInclude(rubric => rubric.SubRubrics)
.ThenInclude(subRubric => subRubric.Items)
.Select(exam => new Result.ExamViewModel
{
Id = exam.Id,
Name = exam.Name,
Stations = exam.Stations.Select(station => new Result.StationViewModel
{
Id = station.Id,
Description = station.Description,
Rubrics = station.Rubrics.Select(rubric => new Result.RubricViewModel
{
Id = rubric.Id,
Name = rubric.Name,
Info = rubric.Info,
SubRubrics = rubric.SubRubrics.Select((subRubric, index) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
})
})
})
})
.ToListAsync()
This provides this error which I don't understand :/
InvalidOperationException: Processing of the LINQ expression '(MaterializeCollectionNavigation(
navigation: Navigation: Rubric.SubRubrics,
subquery: (NavigationExpansionExpression
Source: DbSet<SubRubric>
.Where(s0 => !(s0.IsDeleted))
.Where(s0 => EF.Property<Nullable<long>>(r, "Id") != null && EF.Property<Nullable<long>>(r, "Id") == EF.Property<Nullable<long>>(s0, "RubricId"))
PendingSelector: s0 => (NavigationTreeExpression
Value: (EntityReference: SubRubric | IncludePaths: Items)
Expression: s0)
)
.Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") == EF.Property<Nullable<long>>(i, "RubricId")))
.AsQueryable()
.Select((subRubric, index) => new SubRubricViewModel{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items
.AsQueryable()
.Select(item => new SubRubricItemViewModel{
Id = item.Id,
Name = item.Name
}
)
}
)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
This used to work, until I added the extra SubRubricItems select for the Items model, aka
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
For reference sake, this is the viewmodel that's being projected into:
public sealed class Result
{
public IEnumerable<ExamViewModel> Exams { get; set; }
public sealed class ExamViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<StationViewModel> Stations { get; set; }
}
public sealed class StationViewModel
{
public long Id { get; set; }
public string Description { get; set; }
public IEnumerable<RubricViewModel> Rubrics { get; set; }
}
public sealed class RubricViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public IEnumerable<SubRubricViewModel> SubRubrics { get; set; }
}
public sealed class SubRubricViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
public IEnumerable<SubRubricItemViewModel> Items { get; set; }
}
public sealed class SubRubricItemViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
}
}
That can't be translated to SQL. So either run the SQL query before the .Select(),
.ThenInclude(subRubric => subRubric.Items)
.AsEnumerable()
.Select(exam => new Result.ExamViewModel
or remove the Includes (they don't do anything when you have a custom projection, and thereby change the query)
SubRubrics = rubric.SubRubrics.Select((subRubric) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = 0, . . .
and fill in the Order property on the view models afterwards.

Return entity with sub table collection

Here is what I have:
public class StudentHealthInfoType
{
public int StudentHealthInfoId { get; set; }
public bool? HasAllergies { get; set; }
public List<HealthInfoMedicationType> HealthInfoMedicationType { get; set;}
}
public class HealthInfoMedicationType
{
public int HealthInfoMedicationId { get; set; }
public string MedicationName { get; set; }
}
var result = (from u in context.StudentHealthInfos
from m in context.HealthInfoMedications
where u.RegistrationId == registrationId
&& u.StudentHealthInfoId == m.StudentHealthInfoId
select new StudentHealthInfoType
{ StudentHealthInfoId = u.StudentHealthInfoId,
HasAllergies = u.HasAllergies, HealthInfoMedicationType = new HealthInfoMedicationType
{ HealthInfoMedicationId = m.HealthInfoMedicationId,
MedicationName = m.MedicationName
}
}).FirstOrDefault();
I get this error which is show at HealthInfoMedicationType = new HealthInfoMedicationType
Cannot implicitly convert type
'Dis.QueryManager.HealthFormTypes.HealthInfoMedicationType' to
'System.Collections.Generic.List'
HealthInfoMedicationType needs to be a collection of items returned for one StudentHealthInfoType record. How do I need to setup my objects and then cast them so this query works?
Please try this if you want to query for a particular registration id and list of HealthInfoMedications you can do like this .
var result = context.StudentHealthInfos
.Include(x=>x.HealthInfoMedicationType).FirstOrDefault(f => f.RegistrationId == 1);
// Result will be StudentHealthInfoType with list of HealthInfoMedicationType

Saving one entity having m-t-m relationship duplicate the other entity in the DB?

I have two entities with many to many relationship, I'm saving one entity that has a list of entities, but they get duplicated
ex:
class GENEntity
{
int Id { get; set; }
string Name { get; set; }
List<GENEntityTab> tabs { get; set; }
}
class GENEntityTab
{
int Id { get; set; }
string Name { get; set; }
List<GENEntity> entities { get; set; }
}
When I save object of GENEntity but as a view model as you'll see next, with a list of two GENEntityTab, those two tabs get inserted (duplicated) in the DB. I'm using Web API and angular
In the Repository, it's only called when I click submit (there are some extra properties):
public static JsonViewData AddOrUpdate(ModelDBContext context, GENEntityViewModel entityVM, string name, string id)
{
try
{
var entity = context.Entities.SingleOrDefault(x => x.Id == entityVM.Id);
if (entity == null)
{
entity = new GENEntity();
entity.InjectFrom(entityVM);
context.Entities.Add(entity);
}
else
{
entity.DateUpdated = DateTime.Now;
entity.InjectFrom(entityVM);
}
entity.CreatedById = new Guid(id);
entity.LastUpdatedById = new Guid(id);
context.SaveChanges();
return new JsonViewData { IsSuccess = true, Message = "Created Successfully" };
}
catch (Exception ex)
{
return new JsonViewData { IsSuccess = false, Message = ex.Message };
}
}
the view models:
public class GENEntityTabViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<GENEntity> Entities { get; set; } = new List<GENEntity>();
public GENEntityTabViewModel()
{
}
public GENEntityTabViewModel(GENEntityTab entityTab)
{
Id = entityTab.Id;
Name = entityTab.Name;
Description = entityTab.Description;
Entities = entityTab.Entities;
}
}

EF6: Single relationship table for multiple related entities

I have a EF Model with many entities, like Nodes, Attributes, Tags, etc.
There is also an "Alias" entity, and pretty much every other entity else can have a many-to-many relationship with Aliases. One of the undesired things about this is the number of tables that are created to track these relationships (eg. NodeAlias, AttributeAlias, etc.).
Are there any design alternatives that could map an Alias to all of the other entities in a single table? I was thinking maybe something along these lines if it's possible:
+---------+--------+-------------+-----------+
| AliasId | NodeId | AttributeId | TagId |
+---------+--------+-------------+-----------+
| 1 | 1 | 2 | 3 |
+---------+--------+-------------+-----------+
I updated my solution to provide many-to-many relationships between aliases and every other entity.
I intentionally posted this as a separate answer so that my previous answer can also remain here if anyone would need it.
Step #1: I created extension methods for getting and setting property values using reflection in a convenient way:
public static class ObjectExtensions
{
public static TResult GetPropertyValue<TResult>(this object entity, string propertyName)
{
object propertyValue = entity?.GetType().GetProperty(propertyName)?.GetValue(entity);
try
{
return (TResult)propertyValue;
}
catch
{
return default(TResult);
}
}
public static void SetPropertyValue(this object entity, string propertyName, object value)
{
entity?.GetType().GetProperty(propertyName)?.SetValue(entity, value);
}
}
Step #2: I updated the models to provide many-to-many relationship.
public class Node
{
[Key]
public int NodeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Attribute
{
[Key]
public int AttributeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class AliasMapping
{
[Key]
public int Id { get; set; }
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int? NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int? AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int? TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3: Due to relationship changes the MyDbContext could have been simplified as the [ForeignKey] data annotations are enough.
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
}
Step #4: I also updated the extension methods so that you can create and remove alias mappings.
public static class AliasExtensions
{
public static void CreateMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
if (mappings.Any(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId))
{
// We already have the mapping between the specified entity and alias.
return;
}
bool usableMappingExists = true;
var usableMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == null);
if (usableMapping == null)
{
usableMappingExists = false;
usableMapping = new AliasMapping()
{
Alias = alias
};
}
usableMapping.SetPropertyValue(mappingEntityPropertyName, entity);
usableMapping.SetPropertyValue(entityKeyPropertyName, entityId);
if (!usableMappingExists)
{
context.AliasMappings.Add(usableMapping);
}
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
public static void RemoveMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
AliasMapping currentMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId);
if (currentMapping == null)
{
// There is no mapping between the specified entity and alias.
return;
}
currentMapping.SetPropertyValue(mappingEntityPropertyName, null);
currentMapping.SetPropertyValue(entityKeyPropertyName, null);
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
}
Step #5: Updated the console app steps to align it with the changes.
class Program
{
static void Main(string[] args)
{
// Consider specify the appropriate database initializer!
// I use DropCreateDatabaseAlways<> strategy only for this example.
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
// Always save changes after adding an entity but before trying to create a mapping.
context.SaveChanges();
// One Alias To Many Entities
context.CreateMapping(nodes[0], aliases[0]);
context.CreateMapping(nodes[1], aliases[0]);
context.CreateMapping(nodes[2], aliases[0]);
context.CreateMapping(nodes[3], aliases[0]);
context.CreateMapping(attributes[0], aliases[0]);
context.CreateMapping(attributes[1], aliases[0]);
context.CreateMapping(attributes[2], aliases[0]);
context.CreateMapping(tags[0], aliases[0]);
context.CreateMapping(tags[1], aliases[0]);
// One Entity To Many Aliases
context.CreateMapping(nodes[4], aliases[0]);
context.CreateMapping(nodes[4], aliases[1]);
context.CreateMapping(nodes[4], aliases[2]);
context.CreateMapping(attributes[3], aliases[1]);
context.CreateMapping(attributes[3], aliases[3]);
context.CreateMapping(tags[2], aliases[2]);
context.CreateMapping(tags[2], aliases[3]);
// Remove mapping
context.RemoveMapping(nodes[4], aliases[0]);
// Not really needed here as both 'CreateMapping' and 'RemoveMapping' save the changes
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}
Please note: RemoveMapping() will not delete an AliasMapping even if no entity is associated with it! But CreateMapping() will make use of it later if needed. E.g. look at the screenshot below and check AliasMapping where Id = 5.
Screenshot about the execution result:
You were talking about many-to-many relationship but reading your post I think it is more likely a "special one-to-many" relationship, actually "combined multiple one-to-one" relationship as I see that an Alias can be mapped to a single Node AND/OR to a single Attribute AND/OR to a single Tag.
I think I found a solution for this case.
If it's not the case and an Alias can be mapped to multiple Node AND/OR to multiple Attribute AND/OR to multiple Tag then I think this solution below needs only a small change. :)
Step #1 - These are my example models
public class Node
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Attribute
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
Step #2 - Creating the custom mapping table
public class AliasMapping
{
[Key]
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3 - Creating the DbContext
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Attribute)
.WithOptionalPrincipal(attribute => attribute.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Node)
.WithOptionalPrincipal(node => node.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Tag)
.WithOptionalPrincipal(tag => tag.AliasMapping)
.Map(config => config.MapKey("AliasId"));
}
}
Step #4 - Creating extension method so that creating a relationship will be easy
public static class AliasExtensions
{
public static void CreateMapping<TEntity>(this MyDbContext context, TEntity entity, Alias alias)
{
string mappingEntityPropertyName = typeof(TEntity).Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
bool entityExists = true;
var mapping = context.AliasMappings.Find(alias.AliasId);
if (mapping == null)
{
entityExists = false;
mapping = new AliasMapping()
{
Alias = alias
};
}
typeof(AliasMapping)
.GetProperty(mappingEntityPropertyName)
.SetValue(mapping, entity);
typeof(AliasMapping)
.GetProperty(entityKeyPropertyName)
.SetValue(mapping, typeof(TEntity).GetProperty("Id").GetValue(entity));
if (!entityExists)
{
context.AliasMappings.Add(mapping);
}
}
}
Step #5 - Created a console app to see this working
class Program
{
static readonly Random rnd = new Random(DateTime.Now.TimeOfDay.Milliseconds);
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
context.SaveChanges();
// Associate aliases to attributes
attributes.ForEach(attribute =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Attribute == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(attribute, selectedAlias);
});
// Associate aliases to nodes
nodes.ForEach(node =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Node == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(node, selectedAlias);
});
// Associate aliases to tags
tags.ForEach(tag =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Tag == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(tag, selectedAlias);
});
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}

NullReference error when adding a new model into a model property

I have the following entity framework code first models:
public class Member {
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public String CardNumber { get; set; }
//Foreign Key
public virtual ICollection<Favorite> Favorites { get; set; }
[NotMapped]
public List<SelectListItem> FavoriteTypes { get; set; }
public Member() {
MembersDB db = new MembersDB();
FavoriteTypes = new List<SelectListItem>();
FavoriteTypes.AddRange(db.FavoriteTypes.ToList().Select(f => new SelectListItem { Text = f.Value, Value = f.ID.ToString() }));
}
}
public class FavoriteType {
public int ID { get; set; }
public string Value { get; set; }
}
public class Favorite {
public int ID { get; set; }
public String Value { get; set; }
//Foreign Keys
public virtual FavoriteType FavoriteType { get; set; }
public virtual Member Member { get; set; }
}
This creates a 1-M relationship for FavoriteTypes -> Favorites and 1-M relations for Member -> Favorites
Within my controller action, I retrieve most of the Member's info from Session saved at a couple pages back except for the favorites info which is gathered below. Then I gather the list of ID and input values to add to my new member as so:
[HttpPost]
public ActionResult AddFavs(List<int> ID, List<string> Value) {
MembersDB db = new MembersDB();
Member newMember = (Member)Session["member"];
if (ID != null && Value != null)
{
for (int i = 0; i < ID.Count(); i++)
{
int currentID = ID[i];
var test = new Favorite();
test.FavoriteType = db.FavoriteTypes.Where(f => f.ID == currentID).FirstOrDefault();
test.Value = Value[i];
newMember.Favorites.Add(test);
}
}
While running this code I get a NullReference error on this line newMember.Favorites.Add(test);
Not entirely sure why, any help would be appreciated.
EDIT: while troubleshooting in VS, the only null properties I can find are Favorites in newMember and Member in test
ICollection<Favorite> Favorites is null, so you can't add items to it. You should instantiate it in the constructor of your model:
public Member()
{
Favorites = new List<Favorite>();
// ...
}
Now it's an empty collection and you can add items to it.