Define a LAMBDA query in a property so it can be reused - c#

I'm trying to defined a lambda query in a property of my code first EF model as seen below as, GetLatestTransaction :
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
public TransactionModel GetLatestTransaction {
get {
return Transactions.OrderByDescending(x => x.Created).FirstOrDefault();
}
}
}
The reason for this is that I don't want to have to retype this query in many places and by having it in one place reduce the chances of a bug.
I want to use this in a query like this:
var user = _DB.Users
.Select(u => new UserDetailsView()
{
Id = u.Id,
FirstName= u.FirstName,
LastName= u.LastName,
Balance = u.GetLatestTransaction.ValueResult
}).FirstOrDefault(x => x.Id == userId);
This is however resulting in this error:
System.NotSupportedException: 'The specified type member 'GetLatestTransaction' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
Is there some way to achieve this without storing another relation to the latest transaction on the user and having to update it every time there is a new transaction?
Edit: I would also like to do it as above to avoid making another query to the database, I want it all in one go to improve performance.

Your ApplicationUser class represents the table in the database. It does not represent the usage of the data in the table.
Quite a lot of people think it is good practice to separate the database structure from the usage of the data. This separation is quite often done using the repository pattern. The repository is an abstraction from the internal datastructure of the database. It allows you to add functionality to your classes without demanding this functionality in the control classes that communicate with the database.
There are numerous articles about the repository. This one helped me to understand what functionality I should put in my entity framework classes and which in the repository.
So you'll need a class that represents the elements in your database table and one that represents the applicationUsers with only their LatestTransaction
The class that represents the database table:
class ApplicationUser : IdentityUser
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
}
ApplicationUser with the latest transaction
class AppicationUserExt : <base class needed?>
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public TransactionModel LatestTransaction { get; set; }
}
The function to get your extended ApplicationUser is an extension function of your ApplicationUser. Input: IQueryable<ApplicationUser output: IQueryable<ApplicationUserExt>
static class MyDbContextExtensions
{
// returns ne ApplicationUserExt for every ApplicationUser
public IQueryable<ApplicationUserExt> ToExtendedUsers(this IQueryable<ApplicationUser> applicationUsers)
{
return applicationUsers
.Select(user => new ApplicationUserExt()
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
LatestTransaction = user.Trnasactions
.OrderByDescenting(transaction => transaction.CreationDate)
.FirstOrDefault(),
}
}
}
}
So whenever you have a query with the ApplicationUsers you want, you can use ToExtendedUsers() to get the extended suers
using (var dbContext = new MyDbContext(...))
{
// you wanted to have a query like:
var result dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
// you'll have to add ToExtendedUsers:
var result = dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
.ToExtendedUsers();
}
As the result is still an IQueryable, no query has been done yet. You can still add LINQ statements before the query is done:
var result2 = result
.Where(user.LatestTransaction.Year == 2018)
.GroupBy(user => user.LatestTransaction.Date)
.OrderBy(group => group.Key)
.Take(10)
.ToList();
You see, that you can still do all kinds of LINQ stuff as long as it is an ApplicationUser. As soon as you need the LatestTransaction you convert it to an ApplicationUserExt and continue concatenating your linq statements.

Related

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

Entity code first duplicate entries (MVC Identity)

Until now I have always worked with my own DAL for SQL Server.
In a new project I decided to work with Entity in a MVC project and Identity.
I use to work with bridge tables.
Here is my IdentityModels (simplified)
ApplicationUser
public class ApplicationUser : IdentityUser
{
[Required]
public string Surname { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Group
public class Group
{
[Key]
public int Id { get; set; }
[Display(Name = "Nom du Groupe")]
[Required]
[CustomRemoteValidation("IsGroupNameExist", "Groups", AdditionalFields =
"Id")]
public string Name { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set;
}
And DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet<Group> Groups { get; set; }
}
All tables I need are created and seems well created (ApplicationUser Group and ApplicationUserGroups).
The trouble is :
I have 3 groups (A, B, C) with Id 1,2,3. I"m adding a user in table ApplicationUser with 3 groups in the Groups property.
First part is OK, it adds the good values in the bridge table (ApplicationUsersGroup) BUT It adds groups A, B, C again, with Id 4,5,6 in Group table.
The CreateAsync method of UserManageris not the point (It's the same with just an Add).
If I have a look in the debugger, I can see that when I pass to the add method the user object, in the Groupsproperty, I have a ApplicationUsers property with inside the Groups property. For me, it could be the reason, but if I remove the Groups property from ApplicationUser, code first doesn't create the ApplicationUserGroups.
I'm wrong with something, but what? How can I have a user without an additional entry in Grouptable?
Thank you for your help.
UPDATE
Ok, now I understood why duplicates are added, but in my case, how to avoid that?
Here is the involved part of the Register method:
List<Group> selectedItems = new List<Group>();
foreach (GroupTableViewModel item in model.SelectedGroups)
{
if (item.Selected == true) selectedItems.Add(new Group { Id = item.Id, Name = item.GroupName });
}
var user = new ApplicationUser { Name = model.Name, Surname = model.Surname, UserName = model.Surname + "." + model.Name, Email = model.Email,Groups=selectedItems};
string password = RandomPassword.Generate(8, 8);
var result = await UserManager.CreateAsync(user, password);
CreateAsync() is the identity method. I don't understand how it adds the user (I don't see any Add() or 'SaveChanges() inside with JustDecompile).
Maybe I'm wrong again but if I want to attach an entity to the context I have to create a new context, which will be different from the context used by the CreateAsync() method.
So help needed...
This is a common issue that people unfamiliar with EF face. Because of the disconnected state of entities in the object context, EF will attempt to insert the entities in the relationships, even though they already exist. In order to solve, you need to tell EF that the entities are not new by setting their state to Unchanged. Take a look at this article from Julie Lerman and the related SO question/answer.
https://msdn.microsoft.com/en-us/magazine/dn166926.aspx
Entityframework duplicating when calling savechanges

Many-to-many relationship in EF user instance is NULL [duplicate]

This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 5 years ago.
I'm using .net core 2 mvc, I tried to build many-to-many relationship between Users and Steps.
the relationship is doen but when I query for the record I get user = null.
Hier is my code:
(applicationUser model):
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
(Steps model):
public class Steps
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
StepsUsers model:
public class StepsUsers : IAuditable
{
public int StepId { get; set; }
public Steps Step { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
In DbContext I did this :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<StepsUsers>()
.HasKey(s => new { s.StepId, s.UserId });
builder.Entity<StepsUsers>()
.HasOne(su => su.Step)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.StepId);
builder.Entity<StepsUsers>()
.HasOne(su => su.User)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.UserId);
}
public DbSet<MyApp.Models.StepsUsers> StepsUsers { get; set; }
Now, when I query for an instance of StepsUsers with specific StepId I get all de fields correct except the User field is null
var stepUsers = await _context.StepsUsers.Where(s => s.StepId == id).ToListAsync();
I did the same code for another two tables and it works fine, I don't know why it is like this, any suggestion 1?
The cause of your problems is that your forgot to declare your To-many relations as virtual. Another improvement would be to declare them as virtual ICollection instead of List. After all, what would ApplicationUser.StepUser[4] mean?
If you configure a many-to-many relationship according to the entity framework conventions for many-to-many, you don't need to mention the junction table (StepsUsers). Entity framework will recognize the many-to-many and will create the junction table for you. If you stick to the code first conventions you won't even need the fluent API to configure the many-to-many.
In your design every ApplicationUser has zero or more Steps and every Step is done by zero or more ApplicationUsers.
class ApplicationUser
{
public int Id {get; set;}
// every ApplicationUser has zero or more Steps:
public virtual ICollection<Step> Steps {get; set;}
public string Name {get; set;}
...
}
class Step
{
public int Id {get; set;}
// every Step is performed by zero or more ApplicationUsers:
public virtual ICollection<ApplicationUser> ApplicationUsers {get; set;}
public string Name {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<ApplicationUser ApplictionUsers {get; set;}
public DbSet<Step> Steps {get; set;}
}
This is all entity framework needs to know to recognize that you configured a many-to-many relationship. Entity framework will create the junction table for you and the foreign keys to the junction table. You don't need to declare the junction table.
But how am I suppose to do a join if I don't have the junction table?
The answer is: Don't do the join. Use the collections instead.
If you want all ApplicationUsers that ... with all their Steps that ... you would normally do an inner join with the junction table, and do some group by to get the Application users. Ever tried method syntax to join three tables? They look hideous, difficult to understand, error prone and difficult to maintain.
Using the collections in entity framework your query would be much simpler:
var result = myDbContext.ApplicationUsers
.Where(applicationUser => applicationUser.Name == ...)
.Select(applicationUser => new
{
// select only the properties you plan to use:
Name = applicationUser.Name,
Steps = applicationUser.Steps
.Where(step => step.Name == ...)
.Select(step => new
{
// again fetch only Step properties you plan to use
Name = step.Name,
...
})
.ToList(),
});
Entity framework will recognize that joins with the junction table is needed and perform them for you.
If you want Steps that ... with their ApplicationUsers who ... you'll do something similar:
var result = myDbContext.Steps
.Where(step => ...)
.Select(step => new
{
Name = step.Name,
... // other properties
ApplicationUsers = step.ApplicationUsers
.Where(applicationUser => ...)
.Select(applicationUser => new
{
...
})
.ToList(),
});
In my experience, whenever I think of performing a query with a of DbSets using entity framework, whether it is in a many-to-many, a one-to-many or a one-to-one relation, the query can almost always be created using the collections instead of a join. They look simpler, they are better to understand and thus better to maintain.

Accessing additional properties of an Identity user when retrieving data from the database using EF and Linq

Lets say I added a couple of additional properties to the default User when using asp.net identity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am aware that in my asp.net MVC controller I can simply do the following to get the current logged in user's name:
User.Identity.Name
So when saving to the database I can simply pass User.Identity.Name to my repository along with the object I am saving so the CreatedBy field can be populated.
Now lets say I am retrieving items from the database which have a field of CreatedBy that contains a string of the username, but I want to display Created by : FirstName + LastName in the View.
How do I obtain this extra information? If I was using pure SQL I would do an INNER JOIN on the AspNetUsers table where CreatedBy=Username and simply retrieve the FirstName and LastName in a custom column called CreatedByFullName.
Since I am using Entity Framework now along with the latest version of ASP.NET Identity I am a bit confused at how we are expected to retrieve user information to display in the View of our pages. Is it a matter of doing a join with linq in my repository or simply adding an object to each of my properties called ApplicationUser or is there better ways?
Assumptions:
You have a single tabled called ApplicationUser that contains all your users.
This table has an Id column(int) that you are reusing to store lookups in other tables.
Other classes (what I call uni-directional navigation properties):
public class BookContext : DbContext
{
public DbSet<Book> Books { get; set; }
public Dbset<ApplicationUser> Users { get; set; }
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany()
.HasForeignKey(b => b.CreatedBy);
}
}
public class Book
{
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstBookWithRelatedUser bookContext.Books
.Include(b => b.CreatedByUser)
.First();
}
Something like that. I recommend reading the Entity Framework Documentation. Granted the above code I pretty much just wrote off the top of my head so I may not be exactly right.
If you wanted, what I call, Bi-Directional navigation properties:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
then
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany(u => u.Books)
.HasForeignKey(b => b.CreatedBy);
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstUserWithAllRelatedBooks = bookContext.Users
.Include(u => u.Books)
.First();
}
It really just depends on your needs. But becareful, you can end up with a Giant God DbContext that is aware of all relationships...
Sample EF query will look like below -
var result = (from tab in db.YourTable
join user in db.AspNetUsers on user.username equals tab.CreatedBy
select new {YourTableObj = tab, CreatedByFullName = user.FirstName + " " + user.LastName).ToList();

How to query multiple inherited sub-classes in a single query in Entity Framework

I have a big problem of querying diverse types of inherited subentities in a single query in Entity Framework. My essential aim is providing all of my data model structure in a single JSON string by eager loading. And the tricky point is "the inherited subclasses may contain another inherited subclass". The example seen below will clearly explain the situation.
Assume that I have a simple class structure like this:
public class Teacher
{
public int id { get; set; }
public string fullname{ get; set; }
//navigation properties
public virtual HashSet<Course> courses{ get; set; }
}
public class Course
{
public int id { get; set; }
public string coursename{ get; set; }
//foreign keys
public int TeacherId{ get; set; }
//navigation properties
public virtual Teacher teacher{ get; set; }
public virtual HashSet<Course> prerequisites{ get; set; }
}
Course has some subclasses GradedCourse and UngradedCourse
B1 or B2 may have a list of subentities consists of entities of types B1 or B2.
public class GradedCourse : Course
{
public string gradeType{ get; set; }
}
public class UngradedCourse: Course
{
public string successMetric { get; set; }
}
Now by this structure I want to provide a JSON structure from my WEBApi yielding list of Teacher objects including both GradedCourse and UngradedCourse with their subentities and specific fields. I have a query like this but it does not compile
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
gradeType = g.gradeType
}
).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
successMetric= u.successMetric // subclass specific field
}
)
)
}
)
The problem is concating two different types of objects (they have different fields which is not possible for SQL UNION)
How can I handle this? Any help will open my mind. Thanks in advance for the professionals :)
It does not compile because the element type of 2 sets is not the same. So you just need to make them the same before being able to do anything:
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = true,
gradeTypeOrMetric = g.gradeType
}).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = false,
gradeTypeOrMetric= u.successMetric // subclass specific field
}))
//finally select what of your choice
.Select(e => new {
id = e.id,
coursename = e.coursename,
prerequisites = e.prerequisites,
gradeType = e.isGradedCourse ? e.gradeTypeOrMetric : "",
successMetric = e.isGradedCourse ? "" : e.gradeTypeOrMetric
})
});
You still benefit the query being executed on server side without having to pull all teachers to local (and then being able to cast the entities - which is not supported in LinqToEntity query).

Categories