EF5 code first with ASP.NET Web API: Update entity with many-to-many relationship - asp.net-web-api

I'm trying to update a Customer in my database using ASP.NET Web API and Entity Framework 5 code-first, but it's not working. My entities look like this:
public class CustomerModel
{
public int Id { get; set; }
public string Name { get; set; }
// More fields
public ICollection<CustomerTypeModel> CustomerTypes { get; set; }
}
public class CustomerTypeModel
{
public int Id { get; set; }
public string Type { get; set; }
[JsonIgnore]
public ICollection<CustomerModel> Customers { get; set; }
}
Nothing all that special. I've built a web interface where users can add a customer by supplying the name and checking one or more customer types. When hitting the submit button, the data is sent to my Web API method:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
This updates the customer fields, but the related customer types are ignored. The incoming customer object does contain a list of CustomerTypes it should be associated with:
[0] => { Id: 1, Type: "Finance", Customers: Null },
[1] => { Id: 2, Type: "Insurance", Customers: Null }
[2] => { Id: 3, Type: "Electronics", Customers: Null }
But instead of looking at this list and adding/removing associated entities, EF just ignores it. New associations are ignored and existing associations remain even if they should be deleted.
I had a similar problem when inserting a customer into the database, this was fixed when I adjusted the state of these entities to EntityState.Unchanged. Naturally, I tried to apply this same magic fix in my update scenario:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
foreach (var customertype in customer.CustomerTypes)
{
context.Entry(customertype).State = EntityState.Unchanged;
}
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
But EF keeps displaying the same behavior.
Any ideas on how to fix this? Or should I really just do a manual clear to the list of CustomerTypes and then manually add them?
Thanks in advance.
JP

This is not really solvable by only setting entity states. You must load the customer from the database first including all its current types and then remove types from or add types to the loaded customer according to the updated types collection of the posted customer. Change tracking will do the rest to delete entries from the join table or insert new entries:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
foreach (var typeInDb in customerInDb.CustomerTypes.ToList())
if (!customer.CustomerTypes.Any(t => t.Id == typeInDb.Id))
customerInDb.CustomerTypes.Remove(typeInDb);
// Add new types
foreach (var type in customer.CustomerTypes)
if (!customerInDb.CustomerTypes.Any(t => t.Id == type.Id))
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}

A cleaner solution would be:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
customer.CustomerTypes.Clear();
// Add new types
foreach (var type in customer.CustomerTypes)
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}

Related

Is there a way I can paginate the included list in a linq query

Hope you're doing well,
I was trying to optimize my reads with entity framework, where I arrived at a position, where I get a record from database by id, and I want to include a one-to-many related list, but I don't want to get all data of the list, just a few, so I want to kind of paginate it.
I want to do this process as long as data is in IQueryable state, I don't want to load all data of list in memory and that paginate it as enumerable.
Let's say the query is like below:
var author = await _dbContext.Authors.Where(x => x.Id == id)
.Include(x => x.Books) // <-- paginate this !!??
.FirstOrDefaultAsync();
Entities represent Data state. Pagination and presentation concerns are View state. Entity Framework can help bridge that gap, but it does so by enabling projection so that you can build View state from Data state. Don't pass entities to views, instead build and pass ViewModels to represent the data in accordance to translations and limitations you want for the view.
For instance if you want to pass Author details with their 5 most recent books:
public class AuthorViewModel
{
public int Id { get; set; }
public string Name { get; set; }
// Any other relevant fields...
public ICollection<BookViewModel> RecentBooks = new List<BookViewModel>();
}
public class BookViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime PublishedDate { get; set; }
// Any other relevant fields...
}
var author = await _dbContext.Authors
.Where(x => x.Id == id)
.Select( x => new AuthorViewModel
{
Id = x.Id,
Name = x.Name,
RecentBooks = x.Books
.OrderByDescending(b => b.PublishedDate)
.Select(b => new BookViewModel
{
Id = b.Id,
Name = b.Name,
PublishedDate = b.PublishedDate
}).Take(5)
.ToList()
}).SingleOrDefault();
This gives you the benefit of structuring the data how you want to present it, while generating efficient queries against the database. You can configure tools like Automapper to perform this kind of mapping to use it's ProjectTo<AuthorViewModel>() as a more succinct alternative.

Include() only specific property

Here I am retrieving items and including the creator of the item. The goal is to include only the first and last name from the creator, not the entire user model.
var items = _db.Items.Include("Creator")
The item model has Creator as a navigation property like this:
public User Creator { get; set; }
It works fine, but it loads the entire user model, when really I just want the first name and last name.
How do I specify I only want specific property returned from the user model?
You cannot do that using Include. You can use Select instead:
var items = _db.Items.Select(i => new { Item = i, Creator = new { i.Creator.FirstName, i.Creator.LastName } });
Update
If you need to return that query as method result you have to create a class which could hold the results:
public class ItemWithCreatorNames
{
public Item Item { get; set; }
public string CreatorFirstName { get; set; }
public string CreatorLastName { get; set; }
}
var items = _db.Items.Select(i => new ItemWithCreatorNames { Item = i, CreatorFirstName = i.Creator.FirstName, CreatorLastName = i.Creator.LastName });

Entity Framework Many to Many query

I want to write a simple query, but there are some problems.
I have 2 tables M to N:
Users -> Events.
I want to get all users of a specific event (get this event by eventId).
public IQueryable<User> GetUsersByEventId(int eventId)
{
IQueryable<User> query = this.Context.Users.AsQueryable();
return query.Where(x => x.Events.SingleOrDefault(e => e.EventId == eventId)); ??
}
Something is missing and I dont know what, can someone help me? Thanks a lot !
If I understand you correctly (adding your models would help), I think you want Any
public IQueryable<User> GetUsersByEventId(int eventId)
{
return Context.Users
.Where(u => u.Events.Any(e => e.EventId == eventId));
}
This should return all users who have any event matching the given id.
Note: If you set up your relationships correctly, you should be able to get this directly from the Event.
public class Event
{
...
public virtual ICollection<User> Users { get; set; }
}
So then, you'd get the Event by id and access it's user collection.
var evt = repo.GetEventById(id);
var users = evt.Users;
I suggest you do that in your Event model itself. AFAIK you are using Event, User and EventUsers tables which is standard stuff for many2many.
public class Event
{
public int Id { get; set; }
// ...
public virtual ICollection<EventUsers> EventUsers { get; set; } // This is table that holds EventId, UserId (many2many)
public IQueryable<User> Users { get { return this.EventUsers.Select(x => x.User); } } // Get all users that are in this event
}

implementing dropdownlist in asp.net mvc 3

I am teaching myself asp .net mvc3. I have researched a lot but the more I read the more confused I become. I want to create a page where users can register their property for sale or rent.
I have created a database which looks like this:
public class Property
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
ยทยทยท
public int Furnished { get; set; }
...
}
Now, I want dropdownlistfor = PropertyType and Furnished.
Property type would be
1 Flat
2 House
3 Detached House
...
Furnished would be:
1 Furnished
2 UnFurnished
3 PartFurnished
...
Now, I am really not sure where to keep this information in my code. Should I have 2 tables in my database which store this lookup? Or should I have 1 table which has all lookups? Or should I just keep this information in the model?
How will the model bind to PropertyType and Furnished in the Property entity?
Thanks!
By storing property types and furnished types in the database, you could enforce data integrity with a foreign key, rather than just storing an integer id, so I would definitely recommend this.
It also means it is future proofed for if you want to add new types. I know the values don't change often/will never change but if you wanted to add bungalow/maisonette in the future you don't have to rebuild and deploy your project, you can simply add a new row in the database.
In terms of how this would work, I'd recommend using a ViewModel that gets passed to the view, rather than passing the database model directly. That way you separate your database model from the view, and the view only sees what it needs to. It also means your drop down lists etc are strongly typed and are directly in your view model rather than just thrown into the ViewBag. Your view model could look like:
public class PropertyViewModel
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
public IEnumerable<SelectListItem> PropertyTypes { get; set; }
public int Furnished { get; set; }
public IEnumerable<SelectListItem> FurnishedTypes { get; set; }
}
So then your controller action would look like:
public class PropertiesController : Controller
{
[HttpGet]
public ViewResult Edit(int id)
{
Property property = db.Properties.Single(p => p.Id == id);
PropertyViewModel viewModel = new PropertyViewModel
{
PropertyId = property.Id,
PropertyType = property.PropertyType,
PropertyTypes = from p in db.PropertyTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.PropertyTypeId.ToString()
}
Furnished = property.Furnished,
FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
}
};
return View();
}
[HttpGet]
public ViewResult Edit(int id, PropertyViewModel propertyViewModel)
{
if(ModelState.IsValid)
{
// TODO: Store stuff in the database here
}
// TODO: Repopulate the view model drop lists here e.g.:
propertyViewModel.FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
};
return View(propertyViewModel);
}
}
And your view would have things like:
#Html.LabelFor(m => m.PropertyType)
#Html.DropDownListFor(m => m.PropertyType, Model.PropertyTypes)
I usually handle this sort of situation by using an enumeration in code:
public enum PropertyType {
Flat = 1,
House = 2,
Detached House = 3
}
Then in your view:
<select>
#foreach(var val in Enum.GetNames(typeof(PropertyType)){
<option>val</option>
}
</select>
You can set the id of the option equal to the value of each item in the enum, and pass it to the controller.
EDIT: To directly answer your questions:
You can store them as lookups in the db, but for small unlikely to change things, I usually just use an enum, and save a round trip.
Also look at this approach, as it looks better than mine:
Converting HTML.EditorFor into a drop down (html.dropdownfor?)

Linq to NHibernate projection to anon. type results in mystifying cast error

I have an TaxWork entity which is persisted using NHibernate. This entity has the following properties (among others):
public virtual TaxWorkType Type { get; set; } //Kctc.TaxWorkType is an enumeration
public virtual TaxWorkStatus Status { get; set; } //Kctc.TaxWorkStatus is an enumeration
public virtual LegalWorkPriority Priority { get; set; } //Kctc.LegalWorkType is an enumeration
public virtual User Handler { get; set; } //Kctc.BusinessLayer.Entities.User is another entity
public virtual int? CaseNumber { get; set; }
I am using Linq to NHibernate to pull of a subset of the tax work objects as follows (taxWorkRepository.All obviously returns an IQueryable):
foreach (TaxWork taxWork in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated))
{
...
}
This works fine. I want to use projection in order to query only the columns that are required in this case. I am usnig the following code:
foreach (var taxWorkFragment in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated).Select(x => new { Type = x.Type, DateCreated = x.DateCreated, Handler = x.Handler, Status = x.Status, Priority = x.Priority }))
{
...
}
However, I'm getting the following error when trying to create the anonymous type:
Invalid cast from 'Kctc.TaxWorkStatus' to 'Kctc.BusinessLayer.Entities.User'.
Where on earth is it getting the idea that it should be casting a TaxWorkStatus to a User?
Any suggestions whatsoever what might be going wrong?
Try to make like this:
foreach (var taxWorkFragment in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated)
.Select(x => new TaxWork { Type = x.Type, DateCreated = x.DateCreated, Handler = x.Handler, Status = x.Status, Priority = x.Priority }))
{
...
}
It should help

Resources