Why Entity Framework Core 7.0.2 for SQLite is returning nulls even tho the data exists? - c#

I have been having a hard time trying to figure out why EF6 is not finding a record when I use the Id (which is currently a GUID) even tho the record exists.
The required field is as a String inside the SQLite database and in the model is a GUID. Let me share part of my code to make things more clear:
User Model:
public class User
{
public Guid Id { get; set; } //Todo: Filter users by the Id
public string Password { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
public int AccountActive { get; set; } = 1;
public Role Role { get; set; } = new Role();
}
The method inside the repo that I'm having problems with:
public User GetUser(string Id)
{
if(Guid.TryParse(Id, out Guid result))
{
var user = _context.Users.Include(x => x.Role).SingleOrDefault(u => u.Id == result);
if(user != null)
return user;
}
throw new Exception("User not found!");
}
To me, it looks like EFCore is not able to parse the GUID back to a string to do the correct query. Trying out to see what is going on on the query side I was trying to output the query to the Debug by adding a new string to the appsettings.Development.json like this:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Data Source=Database/PTasks.db"
}
}
The Output is showing much more information but no queries are been shown even after adding the option for sensitive data logging with optionsBuilder.UseSqlite(connectionString).EnableSensitiveDataLogging(true)
So far I have no clues on what is going on since this query has always worked for me over SQL Server, so I tend to think that this might be an issue with the use of EF6 and SQLite. As you can see I have been trying to inspect the Query but with no luck, so right now I'm out of ideas.
When I debug the method the var user = _context.Users.Include(x => x.Role).SingleOrDefault(u => u.Id == result); ends up as null even if the Id param was correctly parsed as a Guid and the Id exists in the database.
Just to add some extra information, I just changed the method to use RawSql and it is returning the expected data, but I really want to understand what is going on.
This is the new version:
public User GetUser(string Id)
{
if(Guid.TryParse(Id, out Guid result))
{
//var user = _context.Users.Include(x => x.Role).SingleOrDefault(u => u.Id == result);
var user = _context
.Users
.FromSqlRaw($"select * from Users where Id = '{Id}'")
.SingleOrDefault();
if(user != null)
return user;
}
throw new Exception("User not found!");
}
Of course, it is missing the join to the Roles table but that is not relevant to the issue.

So... I just found out this is indeed a Bug and it's been around since 2018. the issue arises when you have a PK in a table and that PK is a GUID when you try to filter by the PK, EF does not convert from Guid to String, so basically, the query will be wrong.
The workaround is simple but not very "elegant":
public User GetUser(string Id)
{
if(Guid.TryParse(Id, out Guid result))
{
var user = _context
.Users
.Include(x => x.Role)
.Single(u => u.Id.ToString().Equals(result.ToString()));
if (user != null)
return user;
}
throw new Exception("User not found!");
}
It works... but I hate this ToString() solution!

Related

Many-to-Many EF Core already being tracked - C# Discord Bot

So I'm using Entity Framework Core to build a database of Guilds (Another name for Discord Servers) and Users, with the Discord.NET Library. Each Guild has many users, and each user can be in many guilds. First time using EF and I'm having some teething issues. The two classes are:
public class Guild
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public string Username { get; set; }
public ushort DiscriminatorValue { get; set; }
public string AvatarId { get; set; }
public ICollection<Guild> Guilds { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
With the goal of having 3 tables: Guild, Users, and GuildUsers. This is my current function for getting the guilds:
using var context = new AutomataContext();
var discordGuilds = this.client.Guilds.ToList();
var dbGuilds = context.Guilds;
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users.Select(gu => new User
{
Id = context.Users.AsNoTracking().FirstOrDefault(u => u.Snowflake == gu.Id)?.Id ?? default(int),
}).ToList(),
}).ToList();
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingDbGuild = dbGuilds.AsNoTracking().FirstOrDefault(g => g.Snowflake == guild.Snowflake);
if (existingDbGuild != null)
{
guild.Id = existingDbGuild.Id;
dbGuilds.Update(guild); // Hits the second Update here and crashes
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
I should note, a 'snowflake' is a unique ID that discord uses, but I wanted to keep my own unique ID for each table.
High level overview, guilds are collected into Discord.NET models. These are then transformed into internalGuilds (my guild class, which includes the list of users). Each of these is looped through and upserted to the database.
The issue arises in the second guild loop, where an error is thrown in the "Update" that a User ID is already being tracked (Inside the guild). So the nested ID is already being tracked? Not sure what's going on here, any help would be appreciated. Thanks.
This exception is most likely occurring because you are loading Users without tracking then looping through and potentially trying to update or insert guilds /w the same user reference, especially using the Update method.
I would suggest removing the use of AsNoTracking. Working with detached entity references via AsNoTracking is more of a performance tweak for when reading large amounts of data. You can pre-fetch all of the User references by their snowflake:
using (var context = new AutomataContext())
{
var discordGuilds = this.client.Guilds.ToList();
// Get the user snowflakes from the guilds, and pre-fetch them.
var userSnowflakes = discordGuilds.SelectMany(g => g.Users.Select(u => u.Id)).ToList();
var users = await context.Users
.Where(x => userSnowflakes.Contains(x.Snowflake))
.ToListAsync();
// We need to add references for any New user snowflakes.
var existingSnowflakes = users.Select(x => x.Snowflake).ToList();
// If more detail is needed for new user records, it will need to be fetched from the passed in Guild.User.
var newUsers = userSnowflakes.Except(existingSnowFlakes)
.Select(x => new User { SnowflakeId = x }).ToList();
if(newUsers.Any())
users.AddRange(newUsers);
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users
.Select(gu => users.Single(u => u.Snowflake == gu.Id))
.ToList(),
}).ToList(),
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingGuildId = context.Guilds
.Where(x => x.Snowflake == guild.Snowflake)
.Select(x => x.Id)
.SingleOrDefault();
if (existingGuildId != 0)
{
guild.Id = existingGuildId;
dbGuilds.Update(guild);
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
This should help ensure that the User references for existing users are pointing at the same instances, whether existing users or new user references that will be associated to the DbContext when first referenced.
Ultimately I don't recommend using Update for "Upsert" scenarios, instead since the Db Record needs to be fetched anyways, updating values on the fetched instance or inserting a new one. Update will want to send all fields from an entity to the database each time, rather than just sending what has changed. It means enforcing a bit more control over what can possibly be changed vs. what should not be.

SQLLITE-Xamarin Form - How to check if a value already exist

I looking to check if a value already exists, if exists return true if not false. However, I may be missing something on my logic because is always returning null even if the value exists
sample
public bool ExistPref(int userid)
{
var result = prefDB.db.Table<notificationsPreferences>().Where(t => t.UserId == userid).FirstOrDefault();
Console.WriteLine("user exist "+result+userid);
return result != null;
}
and if the user exist
I would like to update his record otherwise insert the new values
public int UpdateNotifications(notificationsPreferences item)
{
if (item.UserId != 0)
{
return prefDB.db.Update(item);
}
else
{
return prefDB.db.Insert(item);
The issue is here ** the Id comes inside the object item, however for some reason doesn't save the 4 digits id, what gets saved is for example 4 which I assumed is the times I add this item?/ that's why my result is always false because it is not saving the real id .
}
}
use Any
return prefDB.db.Table<notificationsPreferences>().Any(t => t.UserId == userid);
if the user exist I would like to update his record otherwise insert the new values
If you want to insert new record or update record into sqlite, you can take a look the following code:
private void insertorupdate(User model)
{
var data = _SQLiteConnection.Table<User>();
var d1 = data.Where(x => x.userId == model.userId).FirstOrDefault();
if (d1 == null)
{
_SQLiteConnection.Insert(model);
Console.WriteLine("Sucessfully Added");
}
else
{
Console.WriteLine("Already Mail id Exist");
_SQLiteConnection.Update(model);
}
}
public class User
{
[PrimaryKey, AutoIncrement]
public int userId { get; set; }
public string userName { get; set; }
public string password { get; set; }
}

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!

Why is EF core one-to-one relationship not working as expected?

I have a User and a RefreshToken. I need to create a one-to-one relationship between those two objects. My RefreshToken object looks like this
`public class RefreshToken
{
[ForeignKey("User")]
public string RefreshTokenID { get; set; }
public string UserId { get; set; }
public User User { get; set; }
public string Token { get; set; }
}`
My User object looks like this
`public class User : IdentityUser
{
public RefreshToken RefreshToken { get; set; }
}`
Here is how I am persisting the RefreshToken for a user using _refreshTokenRepository:
`public async Task<bool> SaveAsync(User user, string newRefreshToken, CancellationToken ct = default(CancellationToken))
{
if(user.RefreshToken != null) return false;
RefreshToken rt = new RefreshToken(newRefreshToken, DateTime.Now.AddDays(5), user.Id);
await _dbContext.RefreshTokens.AddAsync(rt);
await _dbContext.SaveChangesAsync(ct);
return true;
}`
The issue happens when, user is redirected to the '/refresh' route, and when I need to check the User's refresh token against the one coming back from the client. This property check is always null:
`var user = _userManager.Users.SingleOrDefault(u => u.UserName == username);
if(user == null || user.RefreshToken.Token != refreshToken) return BadRequest();`
The user is in fact the right user and is found, but the RefreshToken property is null.
I have tried using fluent API to create this relationship. Same outcome. The refresh tokens are successfully persisted to the database with the right user ID in the table, but the navigational property is not working. Any ideas why?
You should modify this line:
var user = _userManager.Users.SingleOrDefault(u => u.UserName == username);
By adding .Include(u => u.RefreshToken), So, the line should look like this:
var user = _userManager.Users.Include(u => u.RefreshToken).SingleOrDefault(u => u.UserName == username);
This will tell the store to also load the related RefreshToken entities.

C# Entity Framework with linq returns null reference

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

Categories