I have two entity classes Project and Product with one-to-many association:
public class Product
{
public string Id {get; set;}
public virtual Project Project {get; set;}
}
public class Project
{
public string Id {get; set;}
protected virtual List<Product> Products {get; set;}
public ReadOnlyCollection<Product> GetProducts()
{
return Products.AsReadOnly();
}
public class PropertyAccessExpressions
{
public static Expression<Func<Project, ICollection<Product>>> Products = x => x.Products;
}
}
public class MyDbContext: DbContext
{
public MyDbContext(string connectionString): base(connectionString){}
public DbSet<Project> Projects {get; set;}
public DbSet<Product> Products {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//// project.GetProducts() fails for the following configuratin
//modelBuilder.Entity<Product>()
// .HasRequired(p => p.Project).WithMany(Project.PropertyAccessExpressions.Products);
// The following is OK
modelBuilder.Entity<Project>()
.HasMany(Project.PropertyAccessExpressions.Products).WithRequired(p => p.Project);
}
}
class Program
{
static void Main(string[] args)
{
var context = new MyDbContext(#"data source=localhost;initial catalog=MyTestDb;integrated security=True;");
context.Database.Delete();
context.Database.Create();
var project1 = new Project { Id = "ProjectId1" };
context.Projects.Add(project1);
context.Products.Add(new Product { Id = "ProductId1", Project = project1 });
context.Products.Add(new Product { Id = "ProductId2", Project = project1 });
context.SaveChanges();
var project = context.Projects.ToList()[0];;
var products = project.GetProducts().ToList();
Debug.Assert(products.Count == 2);
}
}
To map the protected property, I use this solution.
But I encountered the following problems:
1) if I configure the one-to-many association between Project and Product with
modelBuilder.Entity<Product>.HasRequied(p => p.Project).WithMany(Project.PropertyAccessExpressions.Products);
Then the Project.GetProducts() fails and it seems the lazy loading does not work.
But if I change to
modelBuilder.Entity<Project>
.HasMany(Project.PropertyAccessExpressions.Products).WithRequired(p => p.Project);
Then everything is OK.
2) If I change the property "Project.Products" from protected to public, then both of the above ways are OK.
What's wrong in this situation?
Properties must be public in order for the proxies to work. See here
Remove the GetProducts and just do this:
public virtual List<Product> Products {get; protected set;}
Notice the protected keyword on the setter. I just tried this and it worked fine for me.
Related
I have an entity user with roles and I need to load the user with our roles (but not proxy object).
class User{
public int Id{get; set;}
public string Name{get; set;}
public ICollection<Role> Roles{get; set;}
}
class Role{
public int Id{get; set;}
public string Name{get; set;}
public virtual User User{get; set;}
}
When I use this: DbContext.Users.Where(x => x.Id == id).Include(x => x.Roles).FirstOrDefault() I get User object with Role Proxy object.
I need User object with Role object.
How to do it? Thanks
Answering your specific answer, just disable proxy creation just for a specific code block, but you will have to eager load your entities like so:
try
{
DbContext.Configuration.ProxyCreationEnabled = false;
var userWithRoles = DbContext.Users.Include(x => x.Roles).Where(x => x.Id == id).FirstOrDefault();
}
finally
{
DbContext.Configuration.ProxyCreationEnabled = true;
}
...which will affect only that instance. I wrapped this in a try finally block, because if any exception occur while loading entities, you can make sure that option will be reversed.
You could also set this globally inside your DbContext constructor, but I don't recommend this:
public class YourDbContext : DbContext
{
public YourDbContext() : base("name=ConnectionString")
{
this.Configuration.ProxyCreationEnabled = true;
}
}
My recommendation is to avoid exposing you database entities to an API. You can create DTO classes and expose them, instead:
// original entities
public class User {
public int Id{get; set;}
public string Name{get; set;}
public ICollection<Role> Roles{get; set;}
// other fields like birthdate, status, password, etc...
}
public class Role {
public int Id{get; set;}
public string Name{get; set;}
public virtual User User{get; set;}
}
// DTO classes, keeping only the fields you want
// original entities
public class UserDTO {
public int Id{get; set;}
public string Name{get; set;}
public ICollection<RoleDTO> Roles{get; set;}
}
public class RoleDTO {
public int Id{get; set;}
public string Name{get; set;}
}
// project them like this:
var userDTO = DbContext.Users.Where(x => x.Id == id)
.Select(u => new UserDTO
{
Id = u.Id,
Name = u.Name,
Roles = u.Roles.Select(r => new RoleDTO
{
Id = r.Id,
Name = r.Name
}),
})
.FirstOrDefault();
Then you could return just the DTO. There are tools like AutoMapper which makes easier and cleaner to project DTO classes, this is just an example.
I have a simple AspNetCore Web Api with 2 entities a Product and a Person which derives from IdentityUser
public class Product
{
public int Id {get;set;}
public string Name {get; set;}
public Person Person {get; set; }
}
public class Person : IdentityUser
{
public string FirstName {get; set;}
}
And the ViewModel and the AutoMapper profile
public class ProductVM
{
public string Name {get; set;}
public string PersonWhoAdded {get;set;}
}
public class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductVM>()
.ForMember(p=> PersonWhoAdded , opt.MapFrom(model=>model.Person.FirstName)).ReverseMap();
}}
And here is my repo
`public class ProductRepo: IProductRepo
{
public IEnumerable<Product> GetProducts()
{
return context.ProductSet.Include(p=> p.Person).ToList();
}
}`
And my controller
[Route("api/[controller]")
public class ProductController: Controller
{
IMapper _mapper;
ctor(mapper){
_mapper=mapper;
}
[HttpGet]
public IActionResult Get()
{
IEnumerable<Product> _products = _repo.GetProducts();
// omitted Status codes for simplicity
return Ok(_mapper.Map<IEnumerable<ProductVM>>(_products);
}
}
The problem is it shows me null on personWhoAdded instead of the FirstName
I can authenticate and do PUSH from different users after I register and login but if I want a get to list the products and the firstName of the User it shows me null ...
Maybe there's a different approach but I am not an expert so any help would do
Thanks
I have an object which can optionally have a reference to a next and/or previous record. Something like this:
public class Foo
{
[Key]
public int Id {get; set;}
[ForeignKey("Previous")]
public int? PreviousId {get; set;}
public Foo Previous {get; set;}
[InverseProperty("Previous")]
public Foo Next {get; set;}
}
Unfortunately this does not work, instead resulting in the error message Unable to determine the principal end of an association between the types Foo and Foo.
The idea is that by setting the PreviousId, the Previous Foo will get its Next set automatically by EF. This is to prevent errors caused by Next and Previous getting out of sync. Also note that PreviousId can be null, in which case no record in the database should have a Next pointing at that record. Is there any way to implement this?
I've managed to achieve what you wanted by using fluent api aproach. I needed to remove PreiousId property from Foo class - it will be added later on by mapping.
public class Foo
{
[Key]
public virtual int Id { get; set; }
public virtual Foo Previous { get; set; }
public virtual Foo Next { get; set; }
}
Change as well all your properties to virtual as this will allow ef to dynamically track state of the properties in the memory. Then inside DbContext derived class you need to override OnModelCreating method and define mapping there:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>()
.HasOptional(f => f.Next)
.WithOptionalPrincipal(f => f.Previous)
.Map(c => c.MapKey("PreviousId"));
base.OnModelCreating(modelBuilder);
}
This will add to Foo table PreviousId column which will be the foreign key of the relationship. It will define 1-0 relationship. If you assign one Foo entity to another's Previous property then assigned entity will have reference to it in Next property. I tested it with the following code:
using(MyDbContext context = new MyDbContext("Test"))
{
context.Database.Delete();
Foo foo1 = context.Foos.Create();
Foo foo2 = context.Foos.Create();
foo1.Next = foo2;
context.Foos.Add(foo1);
context.Foos.Add(foo2);
context.SaveChanges();
}
using (MyDbContext context = new MyDbContext("Test"))
{
Foo foo1 = context.Foos.OrderBy(f => f.Id).First();
Foo foo2 = context.Foos.OrderBy(f => f.Id).Skip(1).First();
// foo1.Next == foo2 and foo2.Previous == foo1
}
For those out there using entity framework core, this is what I wound up doing
public class LinkedListNode
{
public int Id { get; set; }
public int? NextId { get; set; }
public virtual LinkedListNode Next { get; set; }
public int? PrevId { get; set; }
public virtual LinkedListNode Prev { get; set; }
public long SortOrder { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<LinkedListNode>()
.HasOne<LinkedListNode>(x => x.Next)
.WithMany()
.HasPrincipalKey("Id")
.HasForeignKey("NextId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
builder.Entity<LinkedListNode>()
.HasOne<LinkedListNode>(x => x.Prev)
.WithMany()
.HasPrincipalKey("Id")
.HasForeignKey("PrevId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
}
I have the following (abbreviated) DbContext:
public class O19Context : BaseContext<O19Context>
{
public DbSet<PRJ> O19Set { get; set; }
}
[Table("AUFK")]
public class AUFK
{
[ForeignKey("PRJ_GUID")]
public PRJ PRJ {get; set;}
[Key]
public Guid AUFK_GUID {get; set;}
}
[Table("PRJ")]
public class PRJ
{
[Key]
public Guid PRJ_GUID {get; set;}
public IQueryable<AUFK> AUFKS {get; set;}
}
When I run the following code:
var db = new O19Context();
var prj = db.O19Set.Include("AUFKS")
.Single(o => o.PRJ_GUID ==
new Guid("6FE5E97B-9970-4E24-B051-9A710C03A030"));
I get an invalid Include path error. The EntityType PRJ does not declare a navigation property with the name AUFKS.
Where am I going wrong?
Pamela
A good way to see if EF likes your POCO is the EF Power Tools "View Entity Data Model (Read Only)".
http://www.infoq.com/news/2013/10/ef-power-tools-beta4
I made two changes to your code and it now creates a little diagram...
[TestClass]
public class O19Tests
{
[TestMethod]
public void O19Test1()
{
var db = new O19Context();
var prj = db.O19Set.Include("AUFKS")
.FirstOrDefault(o => o.PRJ_GUID ==
new Guid("6FE5E97B-9970-4E24-B051-9A710C03A030"));
}
}
public class O19Context : DbContext//<O19Context>
{
public DbSet<PRJ> O19Set { get; set; }
}
[Table("AUFK")]
public class AUFK
{
//[ForeignKey("PRJ_GUID")] //remove this...
//there is no PRJ_GUID field IN THIS CLASS
public PRJ PRJ { get; set; }
[Key]
public Guid AUFK_GUID { get; set; }
}
[Table("PRJ")]
public class PRJ
{
[Key]
public Guid PRJ_GUID { get; set; }
public IList<AUFK> AUFKS { get; set; } //use IList
}
A navigation property must implement ICollection<T> - you define the property as IQueryable<T> which I do not believe is supported
I'm working with the EF code first approach and want to add a link (map) table. I am working off the below example and get the following error:
System.Data.Entity.Edm.EdmEntityType: : EntityType 'EmployeeDepartmentLink' has no key defined. Define the key for this EntityType.
Problem is I dont want a key on this table it just maps the two tables together:
public class Employee
{
[Key()]
public int EmployeeID;
public string Name;
}
public class Department
{
[Key()]
public int DepartmentID;
public string Name;
}
public class EmployeeDepartmentLink
{
public int EmployeeID;
public int DepartmentID;
}
I have tried a variety of things like adding the "[Key()]" attribute but it doesn't make sense for it to be used, and which field do I add it to? I am wondering if this sort of table model is even supported?
You are trying to make a "Many to Many" mapping.
To perform this, write this code:
public class Employee
{
[Key]
public int EmployeeId;
public string Name;
public List<Department> Departments { get; set; }
public Employee()
{
this.Departments = new List<Department>();
}
}
public class Department
{
[Key]
public int DepartmentId;
public string Name;
public List<Employee> Employees { get; set; }
public Department()
{
this.Employees = new List<Employee>();
}
}
then, in your DbContext:
public class YourContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
public YourContext() : base("MyDb")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().
HasMany(c => c.Employees).
WithMany(p => p.Departments).
Map(
m =>
{
m.MapLeftKey("DepartmentId");
m.MapRightKey("EmployeeId");
m.ToTable("DepartmentEmployees");
});
}
}
For M:M relationship you have to create your join (link) class is as below.
public class EmployeeDepartmentLink
{
[Key, Column(Order = 0)]
public int EmployeeID;
[Key, Column(Order = 1)]
public int DepartmentID;
}
For more information check Create code first, many to many
I hope this will help to you.