Using EF LazyLoading for initializing properties? - c#

UPDATED: I understood I should not use a DbSet so I changed the implementation to an ICollection as suggested by Erenga
Please consider the following classes:
[Table("Tenant")]
public class Tenant : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("User")]
public class User : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
The first test creates a new Tenant and a new User and stores them in the appropriate tables.
[Test]
public void CreateNewUserForNewTenant()
{
var user = _applicationContext.Users.Create();
user.Name = "barney";
user.EmailAddress = "barney#flinstone.com";
var tenant = _applicationContext.Tenants.Create();
tenant.Name = "localhost";
tenant.Guid = Guid.NewGuid().ToString();
tenant.Users.Add(user); // NullReferenceException, I expected the EF would LazyLoad the reference to Users?!
_tenantRepository.Add(tenant);
_applicationContext.SaveChanges();
}
This test will fail on a NullReferenceException since the property Users is not initialized.
How should I change my code that I can rely on LazyLoading provided with EF?

There are 2 problems I see here.
As #SimonWhitehead mentioned, reference types are initialized as null by default. Lazy loading works only on entities created by EF. These are actually sub classes of your class that contain addtional logic to lazy load.
DbSet is not a collection type that is supported on entities. You need to change the type to ICollection, ISet, or IList.
Here's a working example
[Table("Tenant")]
public class Tenant : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("User")]
public class User : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
[Test]
public void CreateNewUserForNewTenant()
{
var user = _applicationContext.Users.Create();
user.Name = "barney";
user.EmailAddress = "barney#flinstone.com";
var tenant = _applicationContext.Tenents.Create();
tenant.Name = "localhost";
tenant.Guid = Guid.NewGuid().ToString();
tenant.Users = new List<User> { user };
_tenantRepository.Add(tenant);
_applicationContext.SaveChanges();
}

var tenant = new Tenant
{
Name = "localhost",
Guid = Guid.NewGuid().ToString(),
Users = new List<User> { user }
};

I think you were expecting something like this (not threadsafe):
[Table("Tenant")]
public class Tenant : IEntity
{
private DbSet<User> _users;
public int Id { get; set; }
public string Name { get; set; }
[Key]
public string Guid { get; set; }
public virtual ICollection<User> Users
{
get
{
if (_users == null)
_users = new List<Users>();
return _users;
}
set { _users = value; }
}
}
I'm sure the Lazy<T> class could be utilised somehow too but I am not familiar with that class.

Related

Creating and updating entities with a many to many relationship

In an ASP.NET core (dotnet 6) web application, we have a small user management functionality in which a user can be assigned to a specific site (physical location) with certain rights. A user can be assigned to one or more sites and a site can also be assigned to one or more users, thus the many to many relationship. We have the following classes:
public class User
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
public class Site
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
The many to many relationship is described in a join table with fluent API:
public class UserSite
{
public int UserID { get; set; }
public User User { get; set; }
public int SiteID { get; set; }
public Site Site { get; set; }
}
// Fluent API for UserSite table
builder.HasKey(us => new { us.UserID, us.SiteID });
builder.HasOne(us => us.User)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.UserID)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(us => us.Site)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.SiteID)
.OnDelete(DeleteBehavior.Cascade);
These classes result in the following DTO's:
public class UserDTO
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public IList<SiteDTO> Sites { get; set; }
}
public class SiteDTO
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
What we want to know is, what is the best way to create a new user or update an existing user? Just to clarify, the data in the Sites table exists already and is static. What I mean by that is that when creating a new user or updating an existing one, a user can be assigned to one or more existing sites, but no new site is created in the process.
We already tried some things that did work but we are not sure if this is really the best way to this or if there is a far better way to do it. Here is what we tried:
The create method
// Create method
public IActionResult CreateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _mapper.Map<User>(userDTO);
user.IsEnabled = true;
AttachSitesToUser(user.ID, user.UserSites);
_context.Users.Add(user);
_context.SaveChanges();
userViewModel = _mapper.Map<UserViewModel>(user);
}
return new JsonResult(new[] { userDTO });
}
The update method:
// Update method
public IActionResult UpdateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _context.Users
.Include(u => u.Sites)
.ThenInclude(s => s.Site)
.Single(u => u.ID == userDTO.ID);
_mapper.Map(userViewModel, user);
AttachSitesToUser(user.ID, user.UserSites);
_context.Entry(user).State = EntityState.Modified;
_context.SaveChanges();
}
return new JsonResult(new[] { userDTO });
}
The helper method AttachSitesToUser:
// Helper method that adds the user ID and attaches the site(s) to the context
private void AttachSitesToUser(int userID, IEnumerable<UserSite> userSites)
{
foreach (UserSite userSite in userSites)
{
userSite.UserID = userID;
if (Context.Entry<Site>(userSite.Site).State == EntityState.Detached)
{
Context.Sites.Attach(userSite.Site);
Context.Entry<Site>(userSite.Site).State = EntityState.Unchanged;
}
}
}
As you can see, for the moment we have to loop through the UserSites list in the User entity and attach the sites the user was assigned to manually to the context. Is there no better way to do this or is this the official best practice?

Relationship 1:N With EF

I have this table
public class Unity
{
public int Id {get;set }
public string Name{ get; set; }
}
public class UsersRight
{
public int Id {get;set }
public string Name{ get; set; }
public int Value{ get; set; }
}
I need a list of all unities that user have access.
I know to do this way:
var userRight = _DAL.UserRights(user).ToList();
var listUser = new List<Unity>;
foreach (var item in userRight)
{
listUser.add( new Unity(Name = item.Name, Id = item.Value));
}
How can I do this in a more efficient way?
In your scenario, the User entity should have a list of Unities:
public virtual ICollection<Unity> Unities { get; set; }
and the Unity entity should have a User:
public virtual User User { get; set; }
You can check this entity framework tutorial on how to configure one-to-many relationship.

EF6 lazy loading not working

I have these objects:
public class Domain : EntityTypeConfiguration<Domain>, IEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public Guid Guid { get; set; }
public ICollection<Website> Websites { get; set; }
}
public class Website: EntityTypeConfiguration<Website>, IEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Range(1, int.MaxValue)]
public int DomainId { get; set; }
public string Description { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreationDate { get; set; }
[Required]
public Guid Guid { get; set; }
[Required]
public string LanguageIds { get; set; }
public bool AllowToSharedTemplates { get; set; }
public int PublishWebsiteId { get; set; }
[Required]
public string WebsiteUrl { get; set; }
public virtual Domain Domain { get; set; }
}
When I want all the websites I want the connected Domains as well (each website has one domain). But somehow this does not work.
public IList<T> GetAll()
{
IList<T> ret;
using (IocDbContext db = Context.CreateContext())
{
DbSet<T> dbSet = db.Set<T>();
ret = dbSet.ToList();
}
return ret;
}
The CreateContext
public IocDbContext CreateContext()
{
IocDbContext rety= new IocDbContext(_siteType.ConnectionString);
rety.Configuration.ProxyCreationEnabled = true;
return rety;
}
As you can see I have a generic repository. It works fine with just one object, but with navigation properties not. With lazy loading, it does not find the navigation property (domain in this case). I get this error:
The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/json; charset=utf-8'.
When I try to map the DTO to an object:
public static Objects.Website ToModel(this Data.Contexts.Website value)
{
if (value == null)
return null;
return new Website
{
Id = value.Id,
Name = value.Name,
Domain = value.Domain?.ToModel()
};
}
Because you have your context wrapped in a using statement, lazy loading would never work because by the time you leave the GetAll() method, the context has been disposed and the connection to the database has been closed.
As much as lazy loading looks like a great option, I would highly recommend against using it unless you know what you're doing. It's much safer to explicitly load the data you need.

Select query in Entity framework

I have two models and a dbcontext:
public class User
{
public int UserId { get; set; }
public String UserName { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
}
public class Conversation
{
public int ConversationId { get; set; }
public String Text { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class ChatContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Conversation> Conversations { get; set; }
}
I am trying to add a conversation entity so I need to retrieve the UserId from User table. So far I have:
var newtext = new Conversation()
{
Text = message, // message is sent from user
UserId = ChatContext.User.Where(u => u.UserName == name).Select(u => u.UserId)
};
I need to query the user table to retrieve the userId associated with that particular name. How should I achieve this? The error is:
ChatContext does not have a definition for user.
Firstly: You must add static to Users like this:
public class ChatContext : DbContext
{
public static DbSet<User> Users { get; set; }
public DbSet<Conversation> Conversations { get; set; }
}
Secondly: You must add FirstOrDefault method to your query, Because it will return an iQueryable like this:
var newtext = new Conversation()
{
Text = message, // message is sent from user
UserId = ChatContext.Users.FirstOrDefault(u => u.UserName == name).UserId
};
This should works.

How to load DTO Within DTOs?

PROBLEM:
I am very new to EF and to LINQ, so please bear with me.
I am trying to create a EF6 model using the database first approach. Simply speaking, I have 2 database tables tblUser and tblMilkMan which have a foreign key relationship on the UserID column.
To avoid cyclic references and to shape the entity data I have created DTO classes for both the models.
I made the MilkManDTO class contain a reference to a UserDTO instance.(This is probably stupid, if so, please guide me to the right way).My aim is to be able to load a milkmen and the related User data
Anyway in my API call, when I try to load a MilkMan by ID, I do not know how to load the related UserDTO. I found examples online on how to load related Entities but not related DTOs.
DB Schema:
Models:
MilkMan Model and DTO:
namespace MilkMan.Models
{
using System;
using System.Collections.Generic;
public partial class tblMilkMan
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual tblUser tblUser { get; set; }
}
public class MilkManDTO
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual UserDTO User { get; set; }
}
}
User Model and DTO:
public partial class tblUser
{
public tblUser()
{
this.tblMilkMen = new HashSet<tblMilkMan>();
}
public int UserID { get; set; }
public string LogonName { get; set; }
public string Password { get; set; }
public int PasswordExpiresAfter { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
:
// more fields
:
public virtual ICollection<tblMilkMan> tblMilkMen { get; set; }
}
public class UserDTO
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Web API Controller Method:
// GET api/MilkMan/5
[ResponseType(typeof(MilkManDTO))]
public async Task<IHttpActionResult> GettblMilkMan(int id)
{
//tblMilkMan tblmilkman = await db.tblMilkMen.FindAsync(id);
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = //???? Error//
}).SingleOrDefaultAsync(b => b.RecordID == id);
if (milkMan == null)
{
return NotFound();
}
return Ok(milkMan);
}
You can nest a new UserDTO and use the same initialization list technique.
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = new UserDTO {
UserID = b.User.UserID,
FirstName = b.User.FirstName,
LastName = b.User.LastName,
}
}).SingleOrDefaultAsync(b => b.RecordID == id);
This code may throw a null reference exception on b.User.UserID if there is not associated User and thus User could be null. You would need to deal with this with either a ?? coalesce, ternary (b.User == null ? "DefaultFirstName" : b.User.FirstName) or omit the entire reference User = (b.User == null ? (UserDTO)null : new UserDTO { ... }). null's make this kind of thing fun.
With C# 6 we have null reference operator .? that makes this much more succinct.

Categories

Resources