C# Entity Framework with linq returns null reference - c#

I have a problem with entity framework in C#.
I have 2 entities, User and UserRole. They are bond by relationships User *->1 UserRole
Whenever I use this query in a function:
User user = context.User.Where(i => i.id == id).FirstOrDefault();
return user.UserRole.accessLevel;
The query returns user, but UserRole is null. The User table has roleId which is related to id of UserRole, and the value of roleId when debugging is correct, although UserRole entity is null. This is strange as it never happened before...
I already made sure that my relationships in model and database are correct. I have correct rows added to database.
EDIT:
Sorry, I should've mentioned I use custom testable database controller:
public class DBController : IUnitOfWork
{
readonly ObjectContext context;
const string ConnectionStringName = "MarketPlaceDBEntities";
public DBController()
{
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
context = new ObjectContext(connectionString);
}
public void Commit()
{
context.SaveChanges();
}
public IObjectSet<Category> Category
{
get { return context.CreateObjectSet<Category>(); }
}
public IObjectSet<ItemComment> ItemComment
{
get { return context.CreateObjectSet<ItemComment>(); }
}
public IObjectSet<ItemRating> ItemRating
{
get { return context.CreateObjectSet<ItemRating>(); }
}
public IObjectSet<Item> Item
{
get { return context.CreateObjectSet<Item>(); }
}
public IObjectSet<ItemSale> ItemSale
{
get { return context.CreateObjectSet<ItemSale>(); }
}
public IObjectSet<ItemScreenshot> ItemScreenshot
{
get { return context.CreateObjectSet<ItemScreenshot>(); }
}
public IObjectSet<UserRole> UserRole
{
get { return context.CreateObjectSet<UserRole>(); }
}
public IObjectSet<User> User
{
get { return context.CreateObjectSet<User>(); }
}
}
And I do operations via it. Maybe it has to do something with my prob.
interface IUnitOfWork
{
IObjectSet<Category> Category { get; }
IObjectSet<ItemComment> ItemComment { get; }
IObjectSet<ItemRating> ItemRating { get; }
IObjectSet<Item> Item { get; }
IObjectSet<ItemSale> ItemSale { get; }
IObjectSet<ItemScreenshot> ItemScreenshot { get; }
IObjectSet<UserRole> UserRole { get; }
IObjectSet<User> User { get; }
void Commit();
}
I had this whole thing working before, but don't know why it went wrong..
EDIT2:
Solved! Thanks RicoSuter.
Enabling lazy loading in constructor of my db controller solved the problem. I thought it was already enabled, because it was set to true in database model, but it looks like that when creating a new context, you have to enable it manually again.
public DBController()
{
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
context = new ObjectContext(connectionString);
context.ContextOptions.LazyLoadingEnabled = true;
}

try to eagerly load UserRole (join):
context.User.Include("UserRole").Where(i => i.id == id).FirstOrDefault();
or enable lazy loading first:
context.ContextOptions.LazyLoadingEnabled = true;
context.User.Where(i => i.id == id).FirstOrDefault();
otherwise there is no relation to a UserRole in your database...

Try this
User user = context.User.Where(i => i.id == id).FirstOrDefault();
return user==null?null:user.UserRole.accessLevel;

Most simply u can do this:
UserRole user = context.User.Where(i => i.id == id).Select(i => i.UserRole);
return user.accessLevel;
Edit:
Assuming you already have a relation between User and UserRole
User user = context.User.Where(i => i.id == id).FirstOrDefault();
UserRole role = context.UserRole.Where(i => user.Contains(i.id)).FirstOrDefault();
return role.accessLevel;
Assuming you dont have a relation between User and UserRole
int roleid = Convert.ToInt32(context.User.Where(i => i.id == id).Select(i => i.roleid));
UserRole role = context.UserRole.Where(i => i.id == roleid).FirstOrDefault();
return role.accessLevel;
Also if you have relation but cant see UserRole under User than try adding this to your model
public IDisposable User()
{
YourDataContext context = new YourDataContext();
Type ct = context.User.GetType();
return (IDisposable)(ct);
}

Related

Problem with selecting from database to complex type using EF Core

I try to use complex type if EF Core.
my table structure
source code
User
-----------
Id (uniqueidentifier)
FirstName (nvarchar(255))
LastName (nvarchar(255))
and my class strcuture is
public class UserId
{
public Guid Value { getl }
private UserId() { }
public UserId (Guid newId) {
//check and assign
}
}
public class User
{
public UserId Id { get; }
public Name Name { get; }
private User() { }
public User(Name name) {
//.... anti corruption
}
}
public class Name
{
public string First { get; }
public string Last { get; }
private Name() { }
public Name(string firstName, string lastName)
{
//anti curruption
}
}
and here is OnModelCreating
modelBuilder.Entity<User>()
.ToTable("User");
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<User>()
.OwnsOne(u => u.Name, un =>
{
un.Property(x => x.First).HasColumnName("FirstName");
un.Property(x => x.Last).HasColumnName("LastName");
});
every thing is work fined when I created a User and Save to database
but when I try to read it from database. the Name property is null
but again when I use .AsNoTracking. It work fined.
(I got from exception but I can't remember how to did it again)
MyDbContext db = new MyDbContext();
var newUser = new User(name: new Name("Foo", "Fighter"));
db.Users.Add(newUser);
db.SaveChanges();
var u1 = db.Users.Take(1).First();
PrintResult("Case 1", u1); //output > Could not read Name from database
var u2 = db.Users.AsNoTracking()
.Take(1).First();
PrintResult("Case 2", u2); //output > Read Name from database success
Console.Read();
my print result method
static void PrintResult(string label, User u)
{
Console.WriteLine($"{label} >>>>>");
if (u.Name == null)
{
Console.WriteLine("Could not read Name from database");
}
else
{
Console.WriteLine("Read Name from database success");
}
}
Can someone tell me that did I do something wrong ?
Why I have to use .AsNoTracking ?
In fact the issue does not reproduce with the posted model here. But it does with the one from the link, and the difference is the type of the PK property in the model - there is no problem when using well known primitive type, but you are using custom id class - yet another DDD "sugar", but with improper/missing equality implementation.
Without implementing value semantics for your id class, EF Core will compare it by reference, thus not finding the "owner PK" needed by the owned entity type. The no tracking queries have no such search, that's why it is "working".
The correct action is to implement value equality semantics in your UserId class (used as a type of User.Id property)
public class UserId
{
public Guid Value { get; }
protected UserId() { }
public UserId(Guid id)
{
if (id == Guid.Empty)
throw new ArgumentException("UserId must not be Empty");
Value = id;
}
}
for instance by adding
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => obj is UserId other && Value == other.Value;

EF change detection does not seem to recognize my updates on an entity

I am trying to create an AddOrUpdate method. Unfortunately, it seems that the change detection is not kicking in. I just don't see what I am doing wrong here.
Consider the following model:
public class Order
{
public long Id { get; set; }
public string OrderId { get; set; }
// ...
public OrderDetails OrderDetails { get; set; }
}
public class OrderDetails
{
public long Id { get; set; }
// ...
public Order Order { get; set; }
}
This entities should have a one-to-one relationship to each other. An order can't exist without OrderDetails and the other way round.
So this is what my EntityTypeConfigurations looks like:
public class OrderEntityTypeConfiguration : EntityTypeConfiguration<Order>
{
public OrderEntityTypeConfiguration()
{
this.HasKey(e => e.Id);
this.HasRequired(e => e.OrderDetails)
.WithRequiredPrincipal(e => e.Order);
this.ToTable("Orders");
}
}
public class OrderDetailEntityTypeConfiguration : EntityTypeConfiguration<OrderDetails>
{
public OrderDetailEntityTypeConfiguration()
{
this.HasKey(e => e.Id);
this.ToTable("OrderDetails");
}
}
Now I've got a repository called OrderRepository, with a method called AddOrUpdate().
public class OrderRepository
{
public OrderRepository(OrderDbContext context)
{
// ...
}
public bool Contains(Order order)
{
var result = this.context.OrderSet.SingleOrDefault(e => e.OrderId == order.OrderId);
return result != null;
}
public Order GetOrderByOrderId(string orderId)
{
return this.context.OrderSet
.Include(e => e.OrderDetails)
.SingleOrDefault(e => e.OrderId == orderId);
}
public void AddOrUpdate(Order order)
{
if (this.Contains(order))
{
var result = this.GetOrderByOrderId(order.OrderId);
result = order;
}
else
{
this.context.OrderSet.Add(order);
}
this.context.SaveChanges();
}
}
However in the case where this.Contains(order) evaluates to true the assignment from order to result does not get detected from the ChangeDetection mechanism. The call to SaveChanges() returns 0. Why is that?
The following approach seems to work, but feels kind of hacky.
public void AddOrUpdate(Order order)
{
if (this.Contains(order))
{
var result = this.GetOrderByOrderId(order.OrderId);
order.Id = result.Id;
order.OrderDetails.Id = result.OrderDetails.Id;
this.context.Entry(result).CurrentValues.SetValues(order);
this.context.Entry(result.OrderDetails).CurrentValues.SetValues(order.OrderDetails);
}
else
{
this.context.OrderSet.Add(order);
}
this.context.SaveChanges();
}
Simple assignment result = order will not work, cause EF is tracking(if chage tracking is enabled) object fetched via this.GetOrderByOrderId(order.OrderId) call and stored in result variable and not the order one. So you will need either copy needed fields from order to result somehow or play with Attach(usually would not recommend though).

EF Core 3 not loading related data in second or deeper level

I'm having a problem loading data from a second level entity using EF Core 3 but it works as expected with EF6 but I'm converting a project to try to migrate the lot to .NET Core, including EF.
I'm not sure if this is the right way to express this but in short, I have a list of companies that have users and these users have roles and for some reason, I can load the companies and their relevant users from a many to many relationship but the roles for each user are not being loaded.
I have the following Generic function:
protected virtual IQueryable<TEntity> GetQueryable(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = null,
bool isCollection = false,
int? skip = null,
int? take = null)
{
IQueryable<TEntity> query = this.Context.Set<TEntity>();
if (filter != null)
{
query = query.Where(filter.Expand());
}
if (includeProperties != null)
{
query = includeProperties
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (current, include) => current.Include(include));
}
if (orderBy != null)
{
query = orderBy(query);
}
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
query = query.AsExpandableEFCore();
return query;
}
and I call it as follows:
var databaseCompanies = await this.UnitOfWork.Companies
.GetAllAsync(null, "Companies.User.Roles");
And my entities are defined as follows:
Company:
public class Company: Entity<Guid>
{
private ICollection<CompanyUsers> _companyUsers;
public virtual ICollection<CompanyUsers> CompanyUsers
{
get => this._companyUsers ?? (this._companyUsers = new HashSet<CompanyUsers>());
set => this._companyUsers = value;
}
}
User:
public class User : Entity<Guid>
{
private ICollection<CompanyUsers> _companyUsers;
private ICollection<UserRole> _roles;
public virtual ICollection<UserRole> Roles
{
get => this._roles ?? (this._roles = new HashSet<UserRole>());
set => this._roles = value;
}
public virtual ICollection<CompanyUsers> CompanyUsers
{
get => this._companyUsers ?? (this._companyUsers = new HashSet<CompanyUsers>());
set => this._companyUsers = value;
}
}
Role:
public class UserRole
{
public Guid UserId { get; set; }
public RoleType Role { get; set; }
public User User { get; set; }
}
Class to define the many to many relation:
public class CompanyUsers
{
public Guid UserId { get; set; }
public User User { get; set; }
public Guid CompanyId { get; set; }
public Company Company { get; set; }
}
As you can see, the Company and User classes have a many to many relation defined by CompanyUsers and there is a one to many relationship between the User the the UserRole classes. When calling:
var databaseCompanies = await this.UnitOfWork.Companies
.GetAllAsync(null, "Companies.User");
It only loads the companies and the users, so I tried this instead:
var databaseCompanies = await this.UnitOfWork.Companies
.GetAllAsync(null, "Companies.User.Roles");
or
var databaseCompanies = await this.UnitOfWork.Companies
.GetAllAsync(null, "Companies.User,Companies.User.Roles");
But none of them work and the roles for the users belonging to the companies never get loaded.
In the last example, it will add 2 .Include but when looking at the query variable but I noticed that expression has 2 arguments but they are different of different type:
1) {value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Data.Entities.Company])}
2) "Company.User.Roles"
Again, if I load only one level below the main entity such as load a specific user, it will load its roles accordingly:
var databaseUser = await this.UnitOfWork.Users
.GetFirstAsync(u => u.Username == username, null, "Roles", true);
Any ideas on how to resolve this?
Thanks.
UPDATE:
As mentioned below, I really wish I could swear right now!! The penny dropped after noticing that the query generated by EF Core was correct and returned the role as part of the query but they were all NULL.
I went to check my Roles table and that's when I realized that it had been wiped when I rolled back a migration regarding roles in order to fix the relationship and I never re-generated the dummy roles that were associated with all the users of the companies I had defined!!
Apologies to all of you who helped!!
You need to include related data using ThenInclude.
Look at this article: https://learn.microsoft.com/en-us/ef/core/querying/related-data
From link:
You can drill down through relationships to include multiple levels of
related data using the ThenInclude method. The following example loads
all blogs, their related posts, and the author of each post. C#
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
In your GetQueryable method parameters replace string includeProperties = null with Func<IQueryable<T>, IIncludableQueryable<T, object>> includes == null and then in the method body update the code as follows:
if (includes != null)
{
query = includes(query);
}
Now when calling the GetQueryable method, pass the value of includes as follows:
sp => sp.Include(i => i.FirstLevel).ThenInclude(f => f.SecondLevel)
Job done! Now It should work as expected!

C# MVC EF - Set a foreign key to NULL?

I am trying to set a foreign key to "Null".. Using the SQL Management Studio it works fine, but trying it using C#, I am getting a exception:
Exception:
Value cannot be null. Parameter name: entity.
Code:
var entity = _db.AlleBenutzer.FirstOrDefault(p => p.Id == id);
if (entity != null)
{
var abteilungObjekt = _db.AlleAbteilungen.FirstOrDefault(p => p.Abteilungsname == abteilung);
var etageObjekt = _db.AlleEtagen.Include(p => p.Abteilung).FirstOrDefault(p => p.Etagenname == etage);
entity.Abteilung = abteilungObjekt;
entity.Etage = etageObjekt;
_db.Entry(entity.Abteilung).State = EntityState.Modified;
_db.Entry(entity.Etage).State = EntityState.Modified;
_db.Entry(entity).State = EntityState.Modified;
_db.SaveChanges();
}
My both models look like that: (cut some pieces)
public class Benutzer
{
public int Id { get; set; }
public Abteilung Abteilung { get; set; }
public Etage Etage { get; set; }
}
public class Abteilung
{
public int Id { get; set; }
public string Abteilungsname { get; set; }
}
Can anyone help me out? How do I modify my model to be able to make my Foreign Keys nullable? Thanks in advance :)
In your model the ID that is acting as your FK should be an int? to make it nullable.
Try:
var entity = _db.AlleBenutzer.FirstOrDefault(p => p.Id == id);
if (entity != null)
{
var abteilungObjekt = _db.AlleAbteilungen.FirstOrDefault(p => p.Abteilungsname == abteilung);
if (abteilungObjekt != null)
{
entity.Abteilung = abteilungObjekt;
_db.Entry(entity.Abteilung).State = EntityState.Modified;
}
var etageObjekt = _db.AlleEtagen.Include(p => p.Abteilung).FirstOrDefault(p => p.Etagenname == etage);
if (etageObjekt != null)
{
entity.Etage = etageObjekt;
_db.Entry(entity.Etage).State = EntityState.Modified;
}
_db.Entry(entity).State = EntityState.Modified;
_db.SaveChanges();
}
As it looks like either abteilungObjekt or etageObjekt may be null when you try to assign them to the entity. You should always perform a null check when using FirstOrDefault() on reference types.

EF Eager Loading Includes Duplicate Entities

I have a User entity and Role entity in a many-to-many relationship. They are injected with Repository instances to be able do lazy loading after the DbContext has been disposed (i.e. outside the Repository layer) like so:
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
// Lazy loaded property
public ICollection<Role> Roles
{
get { return _roles ?? (_roles = Repository.GetRolesByUserId(UserId)); }
set { _roles = value; }
}
private ICollection<Role> _roles;
public IRepository Repository { private get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
// Lazy loaded property
public ICollection<User> Users
{
get { return _users ?? (_users = Repository.GetUsersByRoleId(RoleId)); }
set { _users = value; }
}
private ICollection<User> _users;
public IRepository Repository { private get; set; }
}
public class Repository : IRepository
{
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
// Using 'Include' to eager load the Roles collection for each User
return db.Users.Include(u => u.Roles).ToList();
}
}
public ICollection<Role> GetRolesByUserId(int userId)
{
using (var db = CreateContext())
{
return db.Roles.Where(r => r.Users.Any(u => u.UserId == userId))
.ToList();
}
}
public ICollection<User> GetUsersByRoleId(int roleId)
{
using (var db = CreateContext())
{
return db.Users.Where(u => u.Roles.Any(r => r.RoleId == roleId))
.ToList();
}
}
private CustomContext CreateContext()
{
var db = new CustomContext();
((IObjectContextAdapter)db).ObjectContext.ObjectMaterialized += OnObjectMaterialized;
return db;
}
private void OnObjectMaterialized(object sender, ObjectMaterializedEventArgs args)
{
if (args.Entity is User)
{
(args.Entity as User).Repository = this;
}
if (args.Entity is Role)
{
(args.Entity as Role).Repository = this;
}
}
}
public class CustomContext : DbContext
{
public CustomContext()
: base()
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
When running the following code, for each User entity returned, there are duplicate pairs for each Role entity in user.Roles
IRepository repository = new Repository();
ICollection users = repository.GetAllUsers();
foreach (User user in users)
{
foreach (Role role in user.Roles)
{
...
}
}
The problem occurs regardless of whether or not EF Lazy Loading is enabled, and whether or not the User.Roles property is marked as virtual.
But if I do not eager load Roles in Repository.GetAllUsers() as below and let the lazy-loaded Roles property call Repository.GetRolesByUserId(UserId), then no duplicate Role entities are returned.
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
// No eager loading
return db.Users.ToList();
}
}
If I change the User.Roles property to always hit the Repository, then no duplicate Role entities are returned.
public ICollection<Role> Roles
{
get { return (_roles = Repository.GetRolesByUserId(UserId)); }
set { _roles = value; }
}
It looks like calling db.Users.Include(u => u.Roles) triggers the User.Roles property's get() action which causes the Roles collection to be populated twice.
I've confirmed that the User.Roles property actually gets populated twice when the IQueryable object is enumerated. e.g. when calling .ToList(). This means, in order to work around this behavipur, there's no way to avoid making changes to the Roles property's get() body. Which means putting EF specific logic in your Domain layer and no longer making it data agnostic.
Is there a way to prevent this from happening? Or is there a better way to achieve lazy-loading after the DbContext has been disposed (outside the Repository layer).
Perhaps something like this could work:
public class Repository : IRepository
{
public bool RunningEagerLoading { get; set; } // false by default
public ICollection<User> GetAllUsers()
{
using (var db = CreateContext())
{
try
{
RunningEagerLoading = true;
return db.Users.Include(u => u.Roles).ToList();
// Materializing (by ToList()) is important here,
// deferred loading would not work
}
finally
// to make sure RunningEagerLoading is reset even after exceptions
{
RunningEagerLoading = false;
}
}
}
// ...
}
public class User
{
// ...
public ICollection<Role> Roles
{
get
{
if (Repository.RunningEagerLoading)
return _roles; // Eager loading cares for creating collection
else
return _roles ?? (_roles = Repository.GetRolesByUserId(UserId));
}
set { _roles = value; }
}
private ICollection<Role> _roles;
public IRepository Repository { private get; set; }
}
But it's an ugly trick-programming in my eyes.

Categories