Entity Framework Many to Many query - linq

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
}

Related

EF Code First oddity with Distinct()

There is something I don't fully understand with the code bellow. I'm trying to find the workers that have a duplicate module in their modules collection.
Here's the entities (simplified for the sake of brevity):
public class Worker
{
public int Id { get; private set; }
public ICollection<TakenTrainingModule> TakenTrainingModules { get; private set; }
public Worker()
{
TakenTrainingModules = new HashSet<TakenTrainingModule>();
}
}
public class TakenTrainingModule
{
public int Id { get; set; }
public int TrainingModuleId { get; set; }
}
And here's the query:
var query = from worker in _context.Workers.Include(worker => worker.TakenTrainingModules)
let distinctModules = worker.TakenTrainingModules.Select(module => module.TrainingModuleId).Distinct()
where worker.TakenTrainingModules.Count != distinctModules.Count()
select worker;
With the query bellow, the returned workers have their TakenTrainingModules collection empty.
But, with the next query (without using the keywork let), the collection is fully and correctly loaded:
var query = from worker in _context.Workers.Include(worker => worker.TakenTrainingModules)
where worker.TakenTrainingModules.Count != worker.TakenTrainingModules.Select(module => module.TrainingModuleId).Distinct().Count()
select worker;
What am I missing? Is the let keyword first executing the distinct query and fools the object state manager that the module are loaded but they aren't and the selector doesn't load them next?
Any explanations welcome! :-)
I've updated the query as this:
var query = from worker in _context.Workers
let distinctModules = worker.TakenTrainingModules.Select(module => module.TrainingModuleId).Distinct()
where worker.TakenTrainingModules.Count != distinctModules.Count()
select worker;
return query.Include(worker => worker.TakenTrainingModules).ToArray();
And everything is fine. Thanks to Dismissile for pointing me out a nice answer.

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

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();
}
}

MVC3 entity update issue

We have two models Person and Address. We have created one combined model of these two models as shown below.
public class Trust_Person_Master
{
public Person_Master PersonMaster { get; set; }
public Address_Master AddressMaster { get; set; }
public Trust_Person_Master()
{
}
public Trust_Person_Master(Person_Master personMaster, Address_Master addressMaster)
{
PersonMaster = personMaster;
AddressMaster = addressMaster;
}
}
Now, we use this Trust_Person_Master model, to save person and address.
So following is the method to save and edit.
[HttpPost, Ajax(true)]
public JsonResult SaveTrust(Trust_Person_Master entity)
{
int nCurrPersonId = entity.PersonMaster.Person_ID;
if (entity.PersonMaster.Person_ID > 0)
{
var update = db.Person.Find(entity.PersonMaster.Person_ID);
if (ModelState.IsValid)
{
TryUpdateModel(update);
}
}
else
{
db.Person.Add(entity.PersonMaster);
}
db.SaveChanges();
HttpContext.Application["TrustPersonSearch"] = null;
return Json(new { person_id = entity.PersonMaster.Person_ID, location_id = entity.PersonMaster.Location_ID });
}
But my problem is that when I edit person, i.e. just field of person master say first name, then that it executes the code as required without giving any error. But does not reflect the changes.
You need to show the TryUpdateModel() method. How are you updating the model ?
By looking at the code you have posted everything seems ok and should not give any problems, however I would like to see the way you are updating your entity, the problem seems to lie there.
Debug as much as possible and come back here with your findings.

Using RavenDB, how do I efficiently retrieve a list of items related by a 'foreign key'

I store User objects in RavenDB. Each User has a User.Id property.
I also have a Relationship class that links two User.Ids together to create a Mentor/Mentee relationship, like this:
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
... more properties
}
public class Relationship
{
public string Id { get; set; }
public string MentorId { get; set; }
public string MenteeId { get; set; }
public RelationshipStatus Status { get; set; }
}
Now I want to retrieve a list of Mentees for a given Mentor. I have done this in the following way:
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
var mentees = new List<User>();
db.Query<Relationship>()
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id)));
return mentees;
}
This seems to work just fine but the coding-angel on my shoulder is wrinkling her nose at the smells emanating from the nested use of the IDocumentSession (db) and the need for multiple Load calls to fill the Mentees List.
How can I optimise this method using best practice RavenDB syntax?
Edit
Thanks to #Jonah Himango (see accepted answer below) who solved the problem of multiple calls to the database for me. In addition I have also created a new extension method called 'Memoize' to eliminate the need for the external 'mentees' result List (see code above).
Here is the optimised code. Please feel free to comment and refine further.
The Linq
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
return db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Memoize()
.Select(r => db.Load<User>(r.MenteeId))
.ToList();
}
The extension method
public static List<T> Memoize<T>(this IQueryable<T> target)
{
return target.ToList();
}
Note : This extension method may seem completely superfluous (it is really) but it irritates my geek-gland that I have to call a function called ToList(), not to create a list, but to force the execution of the Linq statement. So my extension method just renames ToList() to the far more accurate Memoize().
You'll want to use the .Include query customization to tell Raven to include the related user object off of each Relationship:
db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id))); // .Load no longer makes a call to the DB; it's already loaded into the session!
Relevant documentation here:
The call to Load() is resolved completely client side (i.e. without
additional requests to the RavenDB server) because the [related]
object has already been retrieved via the .Include call.

Struggling to get AutoMapper to map my ViewModel to my Domain, What could I be doing wrong?

I've been fiddling about and trying multiple things, but I'm going wrong somewhere. I tried to make my first attempt using AutoMapper as simple as possible. I'm trying to create a new Brand and save it to the database, using a CreateBrandViewModel. Some of this might look a bit fruity, but I was trying to get it to work in the simplest way possible.
Domain:
public class Brand : EntityBase
{
public virtual string Name { get; set; } //Not Nullable
public virtual bool IsActive { get; set; } // Not Nullable
public virtual Product DefaultProduct { get; set; } // Nullable
public virtual IList<Product> Products { get; set; } // Nullable
}
ViewModel:
public class CreateBrandViewModel
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
Controller
this is where I've been playing about the most for a while, so it looks a bit strange now. The commented out code hasn't resolved my problem.
[HttpPost]
public ActionResult Create(CreateBrandViewModel createBrandViewModel)
{
if(ModelState.IsValid)
{
Mapper.CreateMap<Brand, CreateBrandViewModel>();
//.ForMember(
// dest => dest.Name,
// opt => opt.MapFrom(src => src.Name)
//)
//.ForMember(
// dest => dest.IsActive,
// opt => opt.MapFrom(src => src.IsActive)
//);
Mapper.Map<Brand, CreateBrandViewModel>(createBrandViewModel)
Session.SaveOrUpdate(createBrandViewModel);
return RedirectToAction("Index");
}
else
{
return View(createBrandViewModel);
}
}
Just for the record, BrandController inherits from SessionController (Ayendes way), and transactions are managed through an ActionFilter. Though thats is a bit irrelevant I think. I've tried various different ways so I have different error messages - if you can take a look at whats happening and tell me how you might expect to use it that would be great.
For reference, my fluent nhibernate mapping for Brand:
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Not.Nullable()
.Length(50);
Map(x => x.IsActive)
.Not.Nullable();
References(x => x.DefaultProduct);
HasMany(x => x.Products);
}
}
Edit 1
I just tried the following code, but putting a breakpoint on Session.SaveOrUpdate(updatedModel) the fields are null and false, when they shouldn't be:
var brand = new Brand();
var updatedBrand = Mapper.Map<Brand, CreateBrandViewModel>(brand, createBrandViewModel);
Session.SaveOrUpdate(updatedBrand);
return RedirectToAction("Index");
}
You appear to be doing your mapping the wrong way around on your return trip from the post. The alternative syntax may help out here, try:
// setup the viewmodel -> domain model map
// (this should ideally be done at initialisation time, rather than per request)
Mapper.CreateMap<CreateBrandViewModel, Brand>();
// create our new domain object
var domainModel = new Brand();
// map the domain type to the viewmodel
Mapper.Map(createBrandViewModel, domainModel);
// now saving the correct type to the db
Session.SaveOrUpdate(domainModel);
let me know if that cracks the egg... or just egg on yer face again :-)

Resources