Entity Framework - set model property value dynamically from a query - c#

I have the following models:
public class A_DTO
{
[Key]
public string Id { get; set; }
**public virtual B_DTO B { get; set; }**
public virtual List<B_DTO> Bs { get; set; }
}
public class B_DTO
{
[Key]
public string Id { get; set; }
public string AId { get; set; }
public string UserId {get; set; }
[ForeignKey("AId"]
public virtual A_DTO A { get; set; }
[ForeignKey("UserId"]
public virtual User User { get; set; }
}
I am trying to get a list of object A_DTO but also including property B:
using AutoMapper.QueryableExtensions;
public IQueryable<A_DTO> GetAllA_DTO()
{
string userId = "8b6e9332-7c40-432e-ae95-0ac052904752";
return context.A_DTO
.Include("Bs")
.Include("B")
.Project().To<A_DTO>()
.Where(a => a.Bs.Any(b => b.UserId == userId));
}
How do I dynamically set this property according to set UserId and A_DTO.Id?

Here is a bag of observations in which you may be lucky enough to find your solution:
The B property in a code first model will result in there being a foreign key in the database table for A_DTOs that contains a reference to the B_DTOs table. Entity Framework will expect to own the responsibility for filling the B navigation property with an object populated with the data from the referenced row in the B_DTOs table, hence you would not be able to change it dynamically.
There is no need to use the Automapper Project method if your source type and destination type are the same. In your example they would both appear to be A_DTO. Are you sure you don't actually intend to have an entity "A" that is included in the context and "A_DTO" that is mapped from "A" via Automapper? If that is what you really want then you could have code in a .Select call mapping A.Bs.FirstOrDefault(b => b.UserId == userId) to A_DTO.B. However, you would not be able to apply filtering on the basis of the userId in an Automapper map.
Without seeing any of the Automapper Map setup code, it is difficult to get an idea of intent here.
As an aside, when using .Include it is better, in my opinion, to use the overload that takes an expression. In your case the includes would be rewritten:
.Include(a => a.B)
.Include(a => a.Bs)
Using this overload ensures that you will get compile time errors if you rename a property but fail to update the string in the .Include statement.

Related

How do I tell EF Core about two properties of the same type?

I have a set of models representing legal cases. One of the actions a user can do on a case is generate a document. This action is saved as a History entity, with an associated HistoryFile entity that contains the data about the file. Other actions may result in a History entity, with zero or multiple associated HistoryFile entities.
Cut-down versions of these two classes looks like this...
public class History {
public int Id { get; set; }
public ObservableCollection<HistoryFile> HistoryFiles { get; set; }
}
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
}
The next requirement is that a user can pick up on a document that was previously generated and continue working on it. The bit where I'm getting stuck is that the HistoryFile entity needs a reference back to the History entity that held the previous version. This means that I need to add two lines of code to the HistoryFile entity...
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
public int? PreviousHistoryId { get; set; }
public virtual History PreviousHistory { get; set; }
}
This means that there are two links from a HistoryFile to a History, one required one which is the parent History entity (via the History property) and an optional one via the PreviousHistory property.
I can't work out how to set this up for EF Core. As the code stands now, when I try to add a migration, I get the following error...
Cannot create a relationship between 'History.HistoryFiles' and 'HistoryFile.PreviousHistory' because a relationship already exists between 'History.HistoryFiles' and 'HistoryFile.History'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'HistoryFile.PreviousHistory' first in 'OnModelCreating'.
I tried adding the following to my DbContext...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
...but it didn't make any difference.
Anyone able to tell me how I configure this so that EF Core knows that there are two distinct links between the two entities?
I'm using EF Core 5.0.7 in a .NET5 project in case it makes a difference.
Thanks
Got it.
I needed to add the following two lines to the History class...
public virtual ICollection<HistoryFile> HistoryFilesParentHistory { get; set; }
public virtual ICollection<HistoryFile> HistoryFilesPreviousHistory { get; set; }
...and then change the code I added to the DbContext to look like this...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFilesParentHistory)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFilesPreviousHistory)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
This worked fine.

EF Core - how to model relation of Grandparent - Parent - Child on same model

Imagine a model of User that can have Parents and also can have Children.
How would you model such a case in EF Core?
I tried with something like that (pseudo-code)
public class User
{
public ICollection<Relation> Relations {get;set;}
public ICollection<User> Parents => Relation.Where(r => r.Relation == 'Parents')
public ICollection<User> Children => Relation.Where(r => r.Relation == 'Children')
}
public class Relaction
{
public User User1 {get;set;}
public Guid User1Id {get;set;}
public User User2 {get;set;}
public Guid User2Id {get;set;}
public Relation Relation {get;set;} //some enum or sth to indicate relation type
}
But in such modeling, I'm not able to force EF DbContext to fetch into User.Relations data where UserId is in User1Id and in User2Id.
Any idea?
What you are asking for is a classic many-to-many self relationship - (1) user as parent can have many users as children, and (2) user as child can have many users as parents.
Thus it is modelled with one main entity and one join (linking) entity similar to what you have shown. The linking entity does not need special indicator because the two FKs determine the role. i.e. lets change your example with more descriptive names:
public class User
{
public Guid Id { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
Now, in pseudo code, given User user, then
user.Parents = db.Users.Where(u => user == u.Child)
user.Children = db.Users.Where(u => user == u.Parent)
EF Core 5.0+ allows you to hide the join entity (it still exists, but is maintained implicitly) and model the relationship with the so called skip navigations, which are the natural OO way of representing such relationship, e.g. the model becomes simply
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
This is all needed to create such relationship.
However the name of the join table and its columns by convention won't be what normally you would do - in this case, they would be "UserUser" table with "ParentsId" and "ChildrenId" columns.
If you use migrations and don't care about the names, then you are done and can safely skip the rest.
If you do care though, luckily EF Core allows you to change the defaults with fluent configuration (even though in a not so intuitive way):
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<Dictionary<string, object>>("UserRelation",
je => je.HasOne<User>().WithMany()
.HasForeignKey("ParentId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.HasOne<User>().WithMany()
.HasForeignKey("ChildId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.ToTable("UserRelations")
.HasKey("ParentId", "ChildId")
);
Here Dictionary<string, object> is the shared type EF Core will use to maintain the join entity in memory (change tracker). And is the most annoying thing in the above configuration since in a future they might change their minds and use different type (there are actually plans to do that in EF Core 6.0), so you'll have to update your mappings. Note that this does not affect the database design, just the memory storage type in EF Core change tracker.
So, because of that and the fact that in some scenarios it is better to work directly with the join entity, you could actually combine both approaches (explicit join entity and skip navigations) and get the best of both worlds.
To do than, you add the explicit entity and (optionally) navigations from/to it. The next is w/o collection navigations from User to UserRelation (with fully defined navigation you would need two ICollection<UserRelation> properties there):
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
and required minimal fluent configuration
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<UserRelation>(
je => je.HasOne(e => e.Parent).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.HasOne(e => e.Child).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.ToTable("UserRelations")
);
The result is the same database model, but with different in-memory representation of the join entity and ability to query/manipulate it directly. Actually you can do the same with implicit entity, but in type unsafe way using sting names and object values which need to be cast to the appropriate type. This probably will improve in the future if they replace Dictionary<string, object> with some generic type, but for now explicit entity combined with skip navigations looks the best.
You can find (I guess better than mine) explanation of all this in the official EF Core documentation - Many-to-many and the whole Relationships section in general.

How to prevent my Include() statement from populating the included entity's collection?

I'm using EF Core 2.1, database first approach. I'm trying to Include() a foreign key entity when fetching my target entity collection, but something strange is happening.
The entity structure is Job -> JobStatus. I'm fetching some Job entities, and want to include the Job's JobStatus foreign key property. The issue is that the JobStatus entity has a ICollection[Job] property that is populating every single Job from the database. This is causing the payload to be gigabytes in size.
When I include the JobStatus on the Job, I'd like to satisfy one of the following solutions. I'm also open to other solutions or workarounds I haven't thought of.
*how can I prevent the JobStatus' ICollection property from populating?
*Or can I prevent Entity Framework from generating that property in the first place?
I've already explored Ignoring the ReferenceLoopHandling
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
Here are the entities, automatically generated by Entity Framework.
public partial class Job
{
public long Id { get; set; }
public long StatusId { get; set; }
public JobStatus Status { get; set; }
}
public partial class JobStatus
{
public JobStatus()
{
Job = new HashSet<Job>();
}
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Job> Job { get; set; }
}
Example code that is causing the problem
var jobs = _context.Set<Job>()
.Where(job => job.Id == 1)
.Include(job => job.Status);
One way to avoid the Job collection from being populated is to explicitly select the columns that you want returned, either through a defined or anonymous type:
var jobs = _context.Set<Job>()
.Where(job => job.Id == 1)
.Include(job => job.Status)
.Select(job => new
{
Id = job.Id,
StatusName = job.Status.Name
});
Add a "virtual" keyword. Any virtual ICollections will be lazy-loaded unless you specifically mark them otherwise.
public virtual ICollection<Job> Job { get; set; }

EntityFreamwork full entity and lite entity

i have table users
user table :
Id, Name , firstName , password , email , address , dateofBrith
i want to create two entity for user table one lite and other full
[Table("user")]
public class LiteUser
{
public int ID {get;set;}
public string Name {get;set;}
public int firstName{get;set;}
}
second entity
public class fullUser : LiteUser
{
public date dateofBrith {get;set;}
public string password {get;set;}
public string email {get;set;}
public string address {get;set;}
}
but not I get error about no column discriminator
is possible to do somthing like my entity are same but one have more filed then the other entity
thank you in advance for help
Unfortunately, no. You can only define one entity to one table. Instead, you'd have to do a manual .Select off of the full entity to return a custom "Lite" entry because EF needs to know all the columns that tie to a specific table from the start.
Edit: The only way around this would be to create a view and map to that instead.
You can do something like this
[Table("user")]
public class LiteUser
{
public string Name {get;set;}
public int firstName{get;set;}
}
public class fullUser : LiteUser
{
public int ID {get;set;}
public date dateofBrith {get;set;}
public string password {get;set;}
public string email {get;set;}
public string address {get;set;}
}
Use primary key public int ID {get;set;} value in the derived class
As Daniel points out, a table can be associated to a single entity definition, outside of Table Per Hierarchy inheritance, which isn't what you are looking for.
This was an old trick I used with NHibernate which isn't supported in EF.
With EF you can utilize Linq and ViewModels to avoid the need of Lite vs. Full models.
Given:
//Entity
public class User
{
public int ID {get;set;}
public string Name {get;set;}
public int firstName{get;set;}
public date dateofBrith {get;set;}
public string password {get;set;}
public string email {get;set;}
public string address {get;set;}
}
// View Models...
public class LiteUserViewModel
{
public int ID {get;set;}
public string Name {get;set;}
public int firstName{get;set;}
}
public class FullUserViewModel : LiteUserViewModel
{
public date dateofBrith {get;set;}
public string password {get;set;}
public string email {get;set;}
public string address {get;set;}
}
Querying..
//Give me a list of lite data..
var viewModels = context.Users
.Where(x => x.DateOfBirth < startDate)
.Select(x => new LiteUserViewModel
{
UserId = x.UserId,
Name = x.Name,
FirstName = x.FirstName
}).ToList();
// Give me a full user.
var viewModel = context.Users
.Where(x => x.UserId = userId)
.Select(x => new FullUserViewModel
{
UserId = x.UserId,
// ... etc ...
}).SingleOrDefault();
You can leverage libraries like AutoMapper to handle mapping entity to view model. In cases where you just want to inspect data you don't need to define a view model / DTO, just use an anonymous type. The end result is the same in that EF will execute an optimized query to just return back the data you want rather than entire entities. You can optimize view models to flatten down hierarchical data using this technique. You do need to ensure that any methods or transformations in the .Select() are pure and EF compatible because EF will attempt to translate and pass those to SQL. More complex transformations should be done in the view model itself, or utilize an anonymous type select of the raw data, followed by a ToList/Single/etc. then .Select() into the view model with appropriate transformations via Linq2Object.
One option is to use table splitting which is when you map a single table to two or more entities. The difference with your requested solution is that the "additional" properties in the "full" configuration will be represented by another entity type. Example (for EF Core; EF6 will be very similar):
public class SplitTablePrincipal
{
[Key]
public int Id { get; set; }
public string PrincipalProperty { get; set; }
// principal entity has a nav property to the dependent entity
public virtual SplitTableDependent Dependent { get; set; }
}
public class SplitTableDependent
{
[Key]
public int Id { get; set; }
public string DependentProperty { get; set; }
}
public class SplitTablePricipalConfiguration : IEntityTypeConfiguration<SplitTablePrincipal>
{
public void Configure( EntityTypeBuilder<SplitTablePrincipal> builder )
{
//builder.HasKey( pe => pe.Id );
// establish 1:? relationship w/ shared primary key
builder.HasOne( pe => pe.Dependent )
.WithOne()
.HasForeignKey<SplitTableDependent>( de => de.Id ); // FK is PK
builder.ToTable( "YourTableName" );
}
}
public class SplitTableDependentConfiguration : IEntityTypeConfiguration<SplitTableDependent>
{
public void Configure( EntityTypeBuilder<SplitTableDependent> builder )
{
//builder.HasKey( de => de.Id );
// map dependent entity to same table as principal
builder.ToTable( "YourTableName" ); // same table name
}
}
You only need to include a DbSet for the SplitTablePrincipal entity type in your DbContext. When querying, the Dependent property will not be populated by default (your "lite" configuration); you would need to eager load the property for the "full" data configuration via .Include( stp => stp.Dependent ). You could also lazy load or explicitly load the Dependent property further down the line should you so choose. For example:
dbContext.Entry( principalEntity ).Reference( p => p.Dependent ).Load();

Entity.HasRequired returning items with NULL property

I have two related entities built and linked with Fluent API.
public class EDeal : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public virtual ECustomer Customer { get; set; }
...etc
}
public class ECustomer : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public string Customer_name { get; set; }
public virtual ICollection<EDeal> Deals { get; set; }
...etc
}
linked with
modelBuilder.Entity<ECustomer>().HasKey(c => c.Customer_id);
modelBuilder.Entity<EDeal>().HasRequired<ECustomer>(s => s.Customer)
.WithMany(r => r.Deals)
.HasForeignKey(s => s.Customer_id);
I recognize that this is inefficient linking but I had to link it in this way because I don't have control over the db structure.
The important thing to note is that the EDeal requires an ECustomer (.HasRequired). The database contains many rows in EDeal that have a null Customer_id field and I do not want to ever pull those lines when I query the entity.
I thought that the .HasRequired would make sure that I never got back any EDeals that do not have ECustomers associated with them but that doesn't seem to be the case. Instead, it only seems to ignore those lines with NULL Customer_id values when I try to order by a property in the Customer. And even then, returning the .Count() of the query behaves strangely.
var count1 = db.Set<EDeal>().Count(); //returns 1112
var count2 = db.Set<EDeal>().ToList().Count(); //returns 1112
var count3 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).Count(); //returns 1112
var count4 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).ToList().Count(); //returns 967
I know I can add a .Where(c => c.Customer.Customer_id != Null) to make sure I only get back what I'm looking for, but I'm hoping for a solution in the Entity's configuration because I have many generic functions acting on my IEntityBase class that build dynamic queries on generic Entities and I don't want to use a workaround for this case.
Questions:
1) Is there a way to limit the entity to only return those EDeals that have a corresponding ECustomer?
2) In my example above, why do count3 and count4 differ?
Thanks in advance.

Categories