Update many-to-many relationship efficiently - asp.net-mvc

I've 2 model classes.
public class Candidate
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Skill> Skills { get; set; }
}
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Candidate> Candidates { get; set; }
}
This creates me 3 tables.
Candidates
Id Name
1 Tom
2 John
Skills
Id Name
1 C#
2 MVC
3 SQL
4 nHibernate
5 Java
I've made the association in the AUTOGENERATED table :
CandidateSkills
CandidateId SkillId
1 2
1 3
Now I've want to update the skills for candidateId 1 i.e. Tom who wants to remove his SkillId : 2 i.e. MVC and add new SkillId : 4 & 5 i.e. nHibernate & Java respectively.
I get the set of Ids from the form i.e. 3, 4 & 5 & and the candidate Id : 1 , Since 2 is removed by the user.
My API is like this :
public Candidate Add(int candidateId, int[] skillIds)
{
var model = dbContext.candidate.Find(candidateId);
}
How do I update the candidate record in an efficient way, making the least database calls?

With many-to-many associations in Entity Framework you can only work with objects, not with primitive Id values.
public void Add(int candidateId, int[] skillIds)
{
var model = dbContext.candidate.Include(c => c.Skills)
.Single(candidateId => c.candidateId == candidateId);
var skills = dbContext.Skills.Where(s => skillIds.Contains(s.Id)).ToArray();
foreach (var skill in skills)
{
model.Skills.Add(skill);
}
dbContext.SaveChanges();
}
You must fetch the candidate with its skills loaded, hence the Include. The skills must be loaded for EF to be able to track changes to the collection.
If it's really a great deal for you to optimize performance you must create a junction class CandidateSkill class in your model, so you can add/delete associations without loading candidate or skill objects.

public class CandidateToSkill
{
public Candidate Candidate{ get; set; }
[Key,Column(Order = 0)]
public int CandidateID { get; set; }
[Key,Column(Order = 1)]
public int SkillID{ get; set; }
public Skill Skill { get; set; }
}
You need also add this class as DbSet in your dbContext class
public class Candidate
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CandidateToSkill> CandidateSkills{ get; set; }
}
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CandidateToSkill> SkillCandidates{ get; set; }
}
and the controller code
public void Add(int candidateId, int[] skillIds)
{
using (var db = new YourdbContext())
{
foreach (int skillID in skillIds )
{
db.CandidateToSkill.Add(new CandidateToSkill(){skillID,candidateId});
}
db.SaveChanges();
}
}
in this sample u have only marking a candidates to add to our context (in your Link-table) and Adding em to db with db.SaveChanges()

You could just mark the entity state as modified and save the context.
public void UpdateCandidate(Candidate candidate)
{
Context.Entry(candidate).State = EntityState.Modified;
}

Related

How to make a ViewModel for an anonymous type from a linq query

I have joined and grouped 2 databases using linq in my controller, and now I need to parse it to the view. I know, I gonna need a ViewModel to do it, but I have no clue what to put in it, to make it work.
My Linq query in the controller looks like this:
var energyUsageViewModel =
from e in applicationDbContext
join u in _context.UsagesPerMonth on e.Id equals u.EnergyUsageId into u2
group u2 by new { Year = e.Year }
into u3
select u3.ToList();
return View(energyUsageViewModel);
In short what the linq query does is taking the "year" from table "e" joining it with table "u" which contains the energy usage per month, which I am gonna use to make a table in my view, which displays the year and usage per month in one row.
And currently my ViewModel looks like this (obviously not working):
public class EnergyUsageViewModel
{
public IEnumerable<UsagePerMonth> UsagePerMonthVm { get; set; }
}
The view takes a model of:
#model IEnumerable<Jullerup.Models.EnergyUsageViewModel>
I have tried to modify the ViewModel to take u3, but haven't succeeded.
I get the following invalid operation exception:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Linq.Enumerable+SelectEnumerableIterator2[System.Linq.IGrouping2[<>f__AnonymousType101[System.String],System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]],System.Collections.Generic.List1[System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]]]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable`1[Jullerup.Models.EnergyUsageViewModel]'.
How do I edit my ViewModel to handle u3?
Update:
I'm working with 3 classes.
Customer class:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
//Navigation Prop
public ICollection<EnergyUsage>? energyUsage { get; set; }
}
EnergyUsage class:
public class EnergyUsage
{
public int Id { get; set; }
public int CustomerID { get; set; }
public DateTime Date { get; set; }
public int YearlyHeatUsage { get; private set; }
public List<UsagePerMonth> UsagePerYear { get; set; }
//Navigation Prop
public Customer? Customer { get; set; }
}
UsagePerMonth class:
public class UsagePerMonth
{
public int Id { get; set; }
public MonthEnum Month { get; set; }
public int Usage { get; set; }
public int HeatUsage { get; private set; }
public string EnergyType { get; set; }
private EnergyMeasurement energyType { get; set; }
public int EnergyUsageId { get; set; }
}
In the database Customer.Id (PK) has a one to many relationship to EnergyUsage.CustomerId (FK) and EnergyUsage.Id (PK) has a one to many relationship to UsagePerMonth.EnergyUsageId (FK).
What I am trying to do, is to get an object/list something I can use in the view to display all instances of UsagePerMonth grouped by Customer.Year for a certain Customer.Id.

SQLite net Extension one to many relationship Windows Phone

I'm using sqlite net extension to create one to many relationship for 2 tables that am using there are tables in my C# code i follow tutorial in official site https://bitbucket.org/twincoders/sqlite-net-extensions
[Table("Stock")]
public class Stock
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(8)]
public string Symbol { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)] // One to many relationship with Valuation
public List<Valuation> Valuations { get; set; }
}
[Table("Valuation")]
public class Valuation
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[ForeignKey(typeof(Stock))] // Specify the foreign key
public int StockId { get; set; }
public DateTime Time { get; set; }
public decimal Price { get; set; }
}
Here is my code to create Stockobject and insert it to Db
var valuation = new List<Valuation>();
valuation.Add(new Valuation { Price = 1 });
var stock = new Stock();
stock.Valuations = valuation;
db.InsertWithChildren(stock);
but when i debug code and read stock data from db it shows me null for list of valuation what is problem here ?
That'is how it shows to me
You have to use db.GetWithChildren<Stock>() instead of db.Table<Stock>().
Example from twincoders:
// Get the object and the relationships
var storedValuation = db.GetWithChildren<Valuation>(valuation.Id);

Entity framework Entities in 'xxContext.x' participate in the 'x_y' relationship error

Before me, one developer used Entity Framework code first. I am not good at EF code first, so when I try to insert data, my code gives this error:
Entities in 'TourismContext.HotelOrders' participate in the 'HotelOrder_Order' relationship. 0 related 'HotelOrder_Order_Target' were found. 1 'HotelOrder_Order_Target' is expected.
This is my insert code:
var hotelOrdersInsert = new Data.Entities.HotelOrder
{
OrderId = odr.ID // this gives 7
HotelID = 13,
StartAt = DateTime.Now, // arrivalDate,
EndAt = DateTime.Now, // departureDate,
PaymentTypeID = 1,
PaymentStatusID = 1,
PaymentIdentifier = "a",
TotalRate = Convert.ToDecimal(total),
CurrencyID = 1
};
db.HotelOrders.Add(hotelOrdersInsert);
db.SaveChanges();
And this is my HotelOrder class:
public class HotelOrder
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public int HotelID { get; set; }
public int OrderId { get; set; }
// other properties
public virtual Order Order { get; set; }
public virtual Hotel Hotel { get; set; }
}
This is my Order class:
public class Order
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public virtual HotelOrder HotelOrder { get; set; }
}
Where can I find the relationship between the order and hotel order models?
You must write this relation as follows:
public class HotelOrder
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public int HotelID { get; set; }
[ForeignKey("Order")]
public int OrderId { get; set; }
public Order Order { get; set; }
[ForeignKey("Hotel")]
public int HotelId { get; set; }
public Hotel Hotel { get; set; }
}
public class Order
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public List<HotelOrder> HotelOrders { get; set; }
}
The problem is the relationship between HotelOrder and Order. It's most likely that you have a 1 to 1 relation. Meaning, Every HotelOrder must have an Order.
The relation is probably defined in the so called fluent API. Check the OnModelCreating(DbModelBuilder) function override of your class derived from DbContext. It probably states something like this:
modelBuilder.Entity<HotelOrder>().HasRequired(hotel_order => hotel_order.Order).WithRequiredPrincipal();
This tells EntityFramework that every HotelOrder must have an Order.
Alternatively, the relationship could be defined as DataAnnotations. Like Elvin Mammadov explained in his answer.
At this point the solution becomes obvious. You need to add an instance of Order to your HotelOrder instance before adding it to db.HotelOrders.

Select multiple table with Linq to Sql

I have two tables. There is one-to-many relationship between these tables.I want to select Company table and BankAccount List table (for appropriate CompanyID).
How can I do it with Linq-to-Sql?
public class Company
{
// My Fields
[Key]
public Guid ID { get; set; }
public string FullName { get; set; }
// My virtual properties FOR relationships(one-to-one,one-to-many etc.).
public virtual List<BankAccount> BankAccounts { get; set; }
}
and
public class BankAccount
{
// My Fields
//[ScaffoldColumn(false)]
[Key]
public Guid ID { get; set; }
[ForeignKey("Companies")]
public Nullable<Guid> CompanyID { get; set; }
public string BankName { get; set; }
// My virtual properties FOR relationships(one-to-one,one-to-many etc.).
public virtual Company Company { get; set; }
}
I write this as follow, but I didn't like it
List<List<BankAccount>> bankaccounts = new List<List<BankAccount>>();
foreach (var comp in companyRepository.Companies)
{
List<BankAccount> banks = new List<BankAccount>();
foreach (var bank in bankRepository.BankAccounts)
{
if (comp.ID == bank.CompanyID)
{
banks.Add(bank);
}
}
bankaccounts.Add(banks);
banks = new List<BankAccount>();
}
I think the following will yield the same result.
var bankaccounts = companyRepository.Companies.Select(c => c.BankAccounts)
.ToList();
If you are using entity framework, you can eagerload the 'BankAccounts' property to minimize DB calls.
Hope this helps.

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Resources