Can't get an entity property - c#

There is a problem with my Db which I figured only now, when I started to work at the web api. My USER entity:
public class User { get; set; }
{
public int UserId { get; set; }
public string Name { get; set; }
}
And this is ACTIVITY
public class Activity
{
public int ActivityId { get; set; }
public User User { get; set; }
}
I added an activity and checked in SSMS. Everything seems to be good, there is a field named UserId which stores the id. My problem is when I try to get a User from an Activity because I keep getting null objects. I didn't set anything special in my DbContext for this.
This is where I'm trying to get an User from an Activity object:
public ActionResult ActivityAuthor(int activityId)
{
Activity activityItem = unitOfWork.Activity.Get(activityId);
return Json(unitOfWork.User.Get(activityItem.User.UserId));
}
Relation between User and Activity

The User property of Activity class should be marked as virtual. It enables entity framework to make a proxy around the property and loads it more efficiently.
Somewhere in your code you should have a similar loading method as following :
using (var context = new MyDbContext())
{
var activity = context.Activities
.Where(a => a.ActivityId == id)
.FirstOrDefault<Activity>();
context.Entry(activity).Reference(a => a.User).Load(); // loads User
}
This should load the User object and you won't have it null in your code.
Check this link for more information msdn

my psychic debugging powers are telling me that you're querying the Activity table without Include-ing the User
using System.Data.Entity;
...
var activities = context.Activities
.Include(x => x.User)
.ToList();
Alternatively, you don't need Include if you select properties of User as part of your query
var vms = context.Activities
.Select(x => new ActivityVM() {UserName = x.User.Name})
.ToList();

Related

Entity Framework loading some, none or all navigation properties dynamically

Looking at this documentation I can see that you can load multiple navigation entities using the following syntax:
using (var context = new DbContext())
{
var userDocs = context.UserDocuments
.Include(userDoc => userDoc.Role.User)
.ToList();
}
This will give me Role and User navigation properties hung off my UserDocument object, however if I want to use the string overload of Include, how might I construct the code to handle multiple includes?
This does not work:
return await ctx.UserDocuments.Where(x => x.UserId == userId)
.Include("Role.User").ToList();
I am trying to do it this way as my methods may want some, all or no navigation properties returned depending on the calling code. My intention is to add a string array to the repository method which will build any required navigation properties accordingly. If this is the wrong approach, does anyone have another recommendation, I'm wondering if lazy loading would be a more elegant solution...?
Edit
This is the Entity which has nav props:
public partial class UserDocument
{
public int Id { get; set; }
public Guid UserId { get; set; }
public int RoleId { get; set; }
public int AccountId { get; set; }
public virtual Role Role { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
I think you are looking for something like this:
public async Task<List<UserDocument>> MyMethod(List<string> propertiesToInclude)
{
IQueryable<UserDocument> currentQuery = _context.UserDocuments.Where(x => x.UserId == userId);
foreach(var property in propertiesToInclude)
{
currentQuery = currentQuery.Include(property);
}
return await currentQuery.ToListAsync();
}
If you're using the Include(String) method, you don't need to include the lambda to specify the property path.
Instead of doing .Include(x => "Role.User"), try .Include("Role.User")
First of all, you must told us what is "Role.User"?
We cannot answer you if we don't know excactly what you wrote.
So, now we are know that are two differents entities you can do this one
var userDocs = await context.UserDocuments
.Include(x => x.Role)
.ThenInclude(x => x.User)
.ToListAsync();
I hope this one helps you. :)

Duplicate key violation occuring even with EntityState set

I am trying to update the values of a project item, specifically the assigned users to the project. The feature is to be able to remove users from an already created project via an edit button. In the function below I am finding the relevant project using a passed in projectID and then finding all users associated to the project. From there I am finding the specific users from the group of users associated to the project which I am looking to remove. Finally the user is removed using the List .Remove() method. (The company value is also found companyFind and assigned to its relevant value)
The AssignedUsersToProject will now only contain users who should be associated to the project once .Remove() is changed. I have looped through and set them to have an Unchanged state, the same goes for the company.
However when saving in the database to update the record the following error is shown despite the use of Unchanged
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_ApplicationUserProject'. Cannot insert duplicate key in object 'dbo.ApplicationUserProject'. The duplicate key value is (5685a830-cb82-4b60-b459-c0852cc74563, 229698cd-eebb-432b-e6eb-08d9b62cc799).
The statement has been terminated.
How could I prevent this from occuring? I thought unchanged would stop the record from being re-inserted into the database on update. Would appreciate any insight into this. Thanks in advance
Project Model
public class Project
{
[Key]
public Guid ProjectId { get; set; }
public Guid companyID { get; set; }
[Required]
public string ProjectName { get; set; }
public ICollection<ApplicationUser> AssignedUsersToProject { get; set; }
public Company assignedCompanyForProject { get; set; }
}
ApplicationUser model
public class ApplicationUser : IdentityUser
{
[JsonIgnore]
public ICollection<Project> Projects { get; set; }
}
Controller function
[HttpPut("removeUser/{username}/{projectID}")]
public async Task<IActionResult> RemoveUserFromProject(string username, Guid projectID)
{
var project = await _context.Projects.FindAsync(projectID);
var query = _context.Users.Where(u => u.Projects.Any(p => p.ProjectId.ToString().Equals(project.ProjectId.ToString()))); //find all users for the project
var companyFind = _context.Companies.First(c => c.CompanyId.ToString().Equals(project.companyID.ToString())); //companyID to current projects company ID
//_context.Attach(project);
project.AssignedUsersToProject = query.ToList();
project.assignedCompanyForProject = companyFind;
var removeUser = query.First(u => u.UserName.Equals(username));
project.AssignedUsersToProject.Remove(removeUser);
foreach (var x in project.AssignedUsersToProject)
{
_context.Entry(x).State = EntityState.Unchanged;
}
_context.Entry(project.AssignedUsersToProject).State = EntityState.Unchanged;
_context.Entry(project.assignedCompanyForProject).State = EntityState.Unchanged;
await _context.SaveChangesAsync();
return NoContent();
}
Edit
Suggested changes
[HttpPut("removeUser/{username}/{projectID}")]
public async Task<IActionResult> RemoveUserFromProject(string username, Guid projectID)
{
var project = _context.Projects.Include(u => u.AssignedUsersToProject).FirstOrDefault(p => p.ProjectId.ToString().Equals(projectID.ToString()));
// remove user where username is match
project.AssignedUsersToProject.Remove(project.AssignedUsersToProject.First(u => u.UserName == username));
}

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.

Filter a property List inside in include

I need your help
I try to create a linq sentence with .Include but my problem is that i have a property in mi class witch is a list, it is my class specifically:
public partial class document
{
public int ID { get; set; }
public string Amount { get; set; }
public List<Log> Log { get; set; }
}
this is the class log
public partial class Log
{
[Key]
public int ID { get; set; }
[Required]
public Status Status { get; set; }
[Column(TypeName = "text")]
public string Description { get; set; }
public DateTime? DateLog { get; set; }
public int? DocumentID{ get; set; }
[ForeignKey("DocumentID")]
public Document Document{ get; set; }
}
my problem is that I don't know how to filter my list record inside the document for include in the class, I need to get the whole document class and filter the log that only shows status = recieved, a document can have many logs
y tried to do that but it didnĀ“t work
var Result = db.document
.Include(m => m.Log.Where(c => c.Status == Status.Recieved));
i recived the next error
"the include path expression must refer to a navigation property defined on the type. use dotted paths for reference navigation properties and the select operator for collection navigation properties.\r\nparameter name: path"
I appreciate your help
Include used for include relationships with an entity and fetch related entity properties, check documentation - Fetching related data
If you select documents without Include like this
var documents = await db.document.ToListAsync();
you get documents data where Log will be null.
You need something like that:
var result = await db.document
.Select(w=> new
{
document = w,
log = w.Log.Where(c => c.Status == Status.Recieved).ToList()
}).ToListAsync();
EF does support some automatic filtering rules to help with concepts like soft-delete (IsActive) and multi-tenancy (ClientId), but not really applicable for scenarios like this where you want to apply a situational filter like "received" documents.
EF entities should be considered as models reflecting the data state. To filter results like that is more of a view model state which you can achieve through projection:
var result = db.document.Select(d => new DocumentViewModel
{
DocumentId = d.DocumentId,
// .. fill in other required details...
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.Select(l => new LogViewModel
{
// Fill needed log details...
}).ToList()
}).ToList();
Otherwise if you are doing something local with the entities and just want the document and the received log entries:
var documentDetails = db.document
.Where(d => d.DocumentId == documentId)
.Select(d => new
{
Document = d,
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.ToList()
}).Single();
documentDetails.Document.Logs will not be eager loaded, and would trigger lazy loading if you access it, but the documentDetails does contain the relevant Received logs to access. As an anonymous type it's not suitable to being returned, only consumed locally.

EF core navigation property return null value even after using Incude

This is not a duplicate question as I have looked up many questions including this, which is the closest to what I want but didn't solve the challenge.
I have my table models relation set up this way:
public class User
{
public long UserId { get; set; }
public string Name { get; set; }
public IList<Transaction> Transactions { get; set; }
}
public class Transaction
{
public long TransactionId { get; set; }
public User User { get; set; }
public User Patient { get; set; }
}
fluent api setup for the entity
//some other modelbuilder stuff
modelBuilder.Entity<User>(entity =>
{
entity.HasMany(e => e.Transactions).WithOne(e => e.User);
//wanted to add another entity.HasMany(e => e.User).WithOne(e => e.Patient) but efcore didn't allow me.
});
This generates a Transaction table with UserUserId and PatientUserId and takes the right values on save.
But when I do a get with a user Id
User user = dbcontext.Set<User>().Include(t => t.Transactions).FirstOrDefault(u => u.UserId == userId);
user.Transactions have a list of transaction all with null Transaction.Patient
What exactly is going on here and how do I get past it?
Thanks.
You are nesting navigations. So, you have to use ThenInclude like this to add Patient which is a navigation property of Transaction.
User user = dbcontext.Set<User>().Include(t => t.Transactions).ThenInclude(p => p.Patient).FirstOrDefault(u => u.UserId == userId);

Categories