I have a "nested" Entity Framework model structure. Here are my models:
class Parent
{
public int ParentId {get;set;}
virtual ICollection<Child> children {get;set;}
}
class Child
{
public int ChildId {get;set;}
public Parent Parent {get;set;}
virtual ICollection<Account> accounts {get;set;}
}
class Account
{
public int AccountId {get;set;}
public Child Child {get;set;}
public string SomeProperty {get;set;}
}
class DbContext
{
public DbSet<Parent> parents {get;set;}
public DbSet<Child> children {get;set;}
public DbSet<Account> accounts {get;set;}
}
How do I change the Account.SomeProperty for all Accounts belonging to a certain Parent with minimal DB queries?
How about this
var db = new DbContext();
var accounts = db.accounts.Where(a => a.Child.Parent.ParentId == parentId);
foreach (var account in accounts)
{
account.SomeProperty = ...;
}
db.SaveChanges();
It will generate just one query (with necessary joins) to retrieve "all Accounts belonging to a certain Parent". How many update statements will be generated is out of your control.
Related
I'm trying to develop a project that includes "online friends" feature using EF Code First. I have to store all the users' friends in the database. I have "User" POCO class for the users and "FriendRelationship" POCO class for holding the friends of a user. These POCO classes are:
[Table("UserTable")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
//other properties
public int? FriendListID {get; set;}
public virtual FriendRelationship FriendList { get; set; }
}
[Table("FriendRelationshipTable")]
public class FriendRelationship
{
public FriendRelationship()
{
Friends = new List<User>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public virtual List<User> Friends { get; set; }
}
And my Fluent API is:
modelBuilder.Entity<User>()
.HasOptional(o => o.FriendList)
.WithMany(m => m.Friends)
.HasForeignKey(fk => fk.FriendListID)
.WillCascadeOnDelete(false);
I'm creating the Friend object in the User object at User object's first creation time(this User object added to database at controller.):
public class CreateAccountModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
//other actions
User _user = new User();
_user.FriendList = new FriendRelationship();
//other actions and return
}
}
And lastly, my friend addition attempt:
static void AddFriendByManually()
{
using (var context = _liveCampusContext)
{
User Admin2 = context.Users.SingleOrDefault(w => w.Username == "Admin2");
User Admin = context.Users.SingleOrDefault(w => w.Username == "Admin");
User AdminVader= context.Users.SingleOrDefault(w => w.Username == "AdminVader");
Admin.FriendList.Friends.Add(Admin2);
Admin.FriendList.Friends.Add(AdminVader);
Admin2.FriendList.Friends.Add(Admin);
Admin2.FriendList.Friends.Add(AdminVader);
AdminVader.FriendList.Friends.Add(Admin2);
AdminVader.FriendList.Friends.Add(Admin);
context.SaveChanges();
}
}
When context.SaveChanges() line executes, an exception is thrown:
Multiplicity constraint violated. The role 'User_FriendList_Target' of the relationship 'TeachLearnWeb.Data.DbContextFolder.User_FriendList' has multiplicity 1 or 0..1.
What am I missing? Thanks.
Judging by the usage example you have shown, this relationship should actually be many to many:
Any User can have many friends.
The same friend can show up for many users.
Also, the friend relationship is bidirectional (I strongly suspect).
So after Admin.FriendList.Friends.Add(AdminVader); the FriendList of AdminVader will already contain Admin and we can skip the line AdminVader.FriendList.Friends.Add(Admin);.
You only need the FriendRelationship class if you want to store additional data on the relationship itself (e.g. FriendSince timestamp).
Else change your model to
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
//other properties
public virtual ICollection<User> Friends { get; set; }
}
And change the config of the relationship as follows:
ModelBuilder.Entity<User>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m => m.ToTable("FriendRelationshipTable"));
As per the example given in your code:
Admin.FriendList.Friends.Add(Admin2);
Admin.FriendList.Friends.Add(AdminVader);
*In the above code you are adding Admin2 and AdminVader.
Admin2.FriendList.Friends.Add(Admin);
Admin2.FriendList.Friends.Add(AdminVader);
* In the above 2 lines you are adding Admin and AdminVader. So AdminVader is being added twice which leads to Multiplicity constraint.
You might need to make changes to your table.
IN the UserTable, you can store all users.
[Table("UserTable")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name {get; set;}
}
In the FriendRelationshipTable, store the userID (user who will have set of friends) and FriendsUserId(this will store the id of the friend the user has)
[Table("FriendRelationshipTable")]
public class FriendRelationship
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public ID
public int FriendUserID { get; set; }
public int UserID {get; set;}
}
I have not added other properties.
Check if this table configuration will work.
I'm new to ASP.NET and currently working on a simple app to show a list of people and their hobbies. I have the following classes:
public class Hobby
{
public int HobbyID {get;set;}
public string Name {get;set;}
public string Type {get;set;}
public ICollection<PersonHobby> PersonHobbies {get;set;}
}
public class Person
{
public int PersonID {get;set;}
public int Age {get;set;}
public string Name {get;set;}
public ICollection<PersonHobby> PersonHobbies {get;set;}
}
public class PersonHobby
{
public int PersonHobbyID {get;set;}
public int PersonID {get;set;}
public int HobbyID {get;set;}
}
When viewing a person's Details page, I also need to display their hobbies. I did some research and found that ViewModels are a good way to accomplish this. So I created one, but I'm not sure if I did it correctly:
public class PersonHobbiesViewModel
{
public Person Person {get;set;}
public IEnumerable<PersonHobby> PersonHobbies {get;set;}
public IEnumerable<Hobby> Hobbies {get;set;}
}
And at this point I know that I need to create a viewmodel object in my controller's Details method and populate it with data, but I don't know how to navigate through the different tables. I have this:
public ActionResult Details(int? id)
{
var viewModel = new PersonHobbiesViewModel();
viewModel.Person = db.Person.find(id);
viewModel.Hobbies = ???
return View(viewModel);
}
On the other hand, if I'm going in the completely wrong direction, let me know! Thanks in advance.
Firstly what you might like to do, is change your entity models ever so slightly and let EF6 deal with the many to many complexity for you.
Your new model might look like this:
public class Hobby
{
public int HobbyID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Person Person { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public ICollection<Hobby> Hobbies { get; set; }
}
Your context might be like:
public class MyContext : DbContext
{
public DbSet<Hobby> Hobbies { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Hobby>()
.HasRequired<Person>(s => s.Person)
.WithMany(s => s.Hobbies)
.HasForeignKey(s => s.PersonId);
}
}
Then when you are creating your model you can simply query it like:
var person = db.People.Include(c => c.Hobbies).SingleOrDefault(x => x.PersonID == id);
var viewModel = new PersonHobbiesViewModel();
viewModel.Person = person;
viewModel.Hobbies = person.Hobbies;
return View(viewModel);
Have a read through the following tutorials about complex entity models and reading from them, they cover the same content.
Ps. I have not tested any of this.
#shenku's answer has correct way to map entities. But it is unnecessary if you don't consider about naming conventions.
Additionally Entity Framework has much of c#'s object oriented programming basics. That means if you pass the person object from your controller to view, you could access to your entities on view like below.
//Controller
var person = db.People.Include(c => c.Hobbies).SingleOrDefault(x => x.PersonID == id);
return View(person);
#*View Page*#
#using Project.Models
#* prints Person Name and Age *#
#Model.Name #Model.age
#* prints Hobby Names of Person
#foreach (var item in Model)
{
#item.name
}
You've answered your own problem correct as "circular reference" on your comment.
So you don't need PersonHobby class, Entity framework will automatically creates this table for you. This magic happens because you've defined hobby and person as a collection on their own classes.
Also you don't need a viewModel for your situation. Just pass the person or hobby object. Do not pass the Icollection because it's already loaded when you wrote db.People.Include(c => c.Hobbies). Also you can define classes as 'virtual' so it will load entities without include method.(Lazy-loading)
I am currently using OData V4 and wish to join 2 tables Account and Product:
The tables are as follows:
Account: Id, Name, Address, ColorCode,
Product: Id, AccountId
AccountId in the Product table is a foreign key mapped to the Id field in the Account table
In my builder I have :
var ProductType= builder.EntityType<Product>();
When I build the Product entity I wish to get the "ColorCode" values from the Account entity.
How can i establish this relationship in the model builder?
I would like the product class to look like this:
public class Product
{
public string Id { get; set; }
public string AccountId { get; set; }
public string ColorCode { get; set; }
}
OData enables you to define relationships between entities. It seems that you're using Web API 2.2 for OData V4 to write your service. In this case you can build the relationship between Products and Accounts like this:
Firstly, in the definition of your POCO classes for Products, you need to add a navigation property for its account(s):
public class Product{
public int Id {get;set;}
public string AccountId {get;set;}
public Account Account {get;set;} // define "Account" as a navigation property of Product
public class Account{
public int Id {get;set;}
public string Name {get;set;}
public Address Address {get;set;} // Address may be a complex type
public int ColorCode {get;set;}
}
Then in the class that inherit from the DbContext, add information about both entities in:
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Account> Accounts { get; set; }
Finally in WebApiConfig.cs, you define the model using ODataConventionModelBuilder according to your need. The model builder will automatically recognize the relationship from the POCO classes and
generate the model for you.
After the service is built, on the client side, a client send such request to get the Product and the ColorCode of its Account:
GET http://host/service/Products?$expand=Account($select=ColorCode)
An example can be viewed here: http://services.odata.org/v4/(S(xrtmlkz1igiqidututicmb2t))/TripPinServiceRW/People?$expand=Trips($select=TripId)
(Running EntityFramework 4.1)
I have a ASP .NET MVC 3 project that has a database with two database tables.
The models that express the database tables:
public class MyDetails
{
public int DetailsID {get;set;}
public string Description {get;set;}
public int
public CarID {get;set;}
public virtual Cars Cars {get;set;}
public PreviousCarID {get;set;}
}
public class Cars
{
public int CarsID {get;set;}
public string CarsName {get;set;}
}
Entity Framework will nicely relate the MyDetails.CarsID and the Cars.CarsID so that I can pull the CarsName.
The issue is that I'm not sure how to relate PreviousCarID to the Cars table.
What I've tried (results in an Entity Framework error):
public PreviousCarID{get;set;}
[ForeignKey("PreviousCarID")]
public virtual Cars Cars2 {get;set;}
I would rename Cars to Car, and change CarsID to CarID. Then leave in your DbContext the DbSet as type Car, but named Cars.
Second, to address your specific problem (using my recommendation above)
public class MyDetails
{
public int DetailsID {get;set;}
public string Description {get;set;}
public int CarID {get;set;}
public int PreviousCarID {get;set;}
public virtual Car Car {get;set;}
public virtual Car PreviousCar {get;set;}
}
public class Car
{
public int CarID {get;set;}
public string CarName {get;set;}
}
You don't say whether you are using Code First, Model First, or Database First. I'll assume Database First. If that's the case, then you need to make sure that both CarID and PreviousCarID are foreign keys to the Car table.
I want to know whether EF CodeFirst will automatically track "child" objects in the example below.
var db = MyDataContext();
var order = db.Orders.Find(orderId);
order.AddOrderLine("Fancy Product");
db.Commit();
Here are my (simplified) domain entities
public class OrderLine {
public Guid OrderLineId { get; private set; }
public Guid OrderId { get; private set; }
public string Description { get; private set; }
public OrderLine(Guid orderId, string description) {
OrderLineId = Guid.NewGuid();
OrderId = orderId;
Description = description;
}
}
public class Order : Aggregate {
public Guid OrderId { get; private set; }
public ICollection<OrderLine> OrderLines { get; private set; }
public void AddOrderLine(string description) {
OrderLines.Add(new OrderLine(OrderId, description));
}
}
Yes, when you get your Order from context and add the new OrderLine, DbContext will insert it to database calling SaveChanges. It will also track all changes to loaded OrderLines. The only exception can be deleting existing OrderLine. If your OrderLine has PK only OrderLineId removing OrderLine from Order.OrderLines collectin will not delete OrderLine in database but instead it will set its OrderId to null (= exception in your case). If both OrderLineId and OrderId are PK in your OrderLine entity removing OrderLine from Order.OrderLines will also delete OrderLine in database.