Entity framework - load object with related object - entity-framework

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.

Related

Mapping IdentityUser derived to a concrete class with AutoMapper

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

Creating a double linked list in Entity Framework

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

Problems when mapping a private property

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.

When Entity Framework auto generated property gets populated

Consider I have the following classes:
class User
{
[Key]
public Guid Id {get; set;}
public string UserName {get; set;}
}
class MyUsersContext: DbContext
{
DbSet<User> Users {get; set;}
}
class MyRepository
{
MyUsersContext _db; //INJECTED
User AddUser(string userName)
{
var newBorn = new User() { UserName = userName };
_db.Users.Add(newBorn);
return newBorn;
}
}
class MyMemProvider: MembershipProvider
{
MyRepository repo; //INJECTED
public override MembershipUser CreateUser(string username...
{
var ret = repo.AddUser(username);
return new MembershipUser(System.Web.Security.Membership.Provider.Name, ret.UserName...
}
}
This is a stripped down version of my custom membership provider for ASP.NET MVC3 site. At what point User.Id gets filled with actual Guid in this scenario?
You have to apply DatabaseGeneratedOption on your Id attribute.
class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get; set;}
public string UserName {get; set;}
}

Multiplicity constraint violated SQL Server 2008 - CodeFirst

I'm working to solve a very tedious problem.
I have a class called Nation and a class called NationAlly
public class Nation
{
public int ID {get; set;}
public int name {get;set;}
public List<NationAlly> NationAllies {get;set;}
}
public class NationAlly
{
public int ID {get; set;}
public int level {get;set;}
public Nation toNation {get;set;}
}
I'm using EF 4 and CodeFirst with a DbContext called NationsDB to manage my database on SQL Server 2008.
If I create a new object of type Nation and I try to call nationsDB.SaveChanges, I got the following exception:
"Multiplicity constraint violated. The role 'NationAlly_toNation_Target' of the relationship 'CodeFirstNamespace.NationAlly_toNation' has multiplicity 1 or 0..1."
I tried to save a Nation with NationAllies field null and this exception is not thrown, the nation table in the database gets all the correct values.
In my database the table Nation has 2 fields: ID(primary key), name
The table NationAlly has 3 fields: ID(primary key), level, NationID
The two tables are linked with a relationship where NationAlly.NationID is foreign key and Nation.ID is primary key.
Isn't strange? In my eyes the table NationAlly should have a field called NationID1 and another called NationID2 to create the "relationship" between a nation and a list of other nations.
What did I do wrong?
You are perhaps a victim of the EF Code-First mapping conventions which create automatically a relationship between NationAllies and toNation you don't want to have.
If I understand you correctly (but I am not 100 percent sure, if I do), you actually want to have two relationships and you have exposed only one end of the relationship in each of the entities. So, NationAllies does NOT point to toNation but to an "invisible" Owner nation in your NationAlly entity.
If that is the case you need to explicitly overwrite the convention mappings. In the Fluent API of EF 4.1 this could look like:
public class MyContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
This mapping would create the two foreign keys OwnerID and NationID in the NationAllies table, both pointing to the primary key ID in the Nations table.
Edit
Here is the application I have tested with:
Create a new Console App in VS2010 / .NET 4.0, name it "NationsApp"
Add a reference to "EntityFramework.dll"
Clear the content of "Program.cs" and paste instead the following in:
Content of Program.cs:
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace NationsApp
{
public class Nation
{
public int ID { get; set; }
public int name { get; set; }
public List<NationAlly> NationAllies { get; set; }
}
public class NationAlly
{
public int ID { get; set; }
public int level { get; set; }
public Nation toNation { get; set; }
}
public class NationsContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new NationsContext())
{
try
{
// We have three Nations and two Allies
Nation nation1 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation2 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation3 = new Nation() {
NationAllies = new List<NationAlly>() };
NationAlly ally1 = new NationAlly();
NationAlly ally2 = new NationAlly();
// Nation1 has two Allies
// (Nation1 is the "owner" of both Allies)
nation1.NationAllies.Add(ally1);
nation1.NationAllies.Add(ally2);
// toNation of ally1 refers to Nation2
ally1.toNation = nation2;
// toNation of ally2 refers to Nation3
ally2.toNation = nation3;
context.Nations.Add(nation1);
context.Nations.Add(nation2);
context.Nations.Add(nation3);
context.SaveChanges();
}
catch (Exception e)
{
throw;
}
}
}
}
}
You can set a breakpoint on "throw" to watch possible exceptions in e in the debugger.
This creates a database called NationsApp.NationsContext if you are using SQL Server Express and don't have any further connection strings defined.
It gives two relationships Nation_NationAllies (FK is "OwnerID") and NationAlly_toNation (FK is "NationID"). All columns are non-nullable. The result in the DB is the following:
In case this helps someone getting this error... I was getting this message while doing queries rather than saving to the database. My data design:
public class Base {
public int Id {get; set;}
}
public class Child {
[Key][ForeignKey("Base")] public int Id {get; set;}
public virtual Base Base {get; set;}
public Child() {
Base = new Base();
}
}
The problem was in the constructor. Turns out EF4.1 doesn't like when you initialize associations there! I removed that constructor and things started working again.