If I set LazyLoad on multiple properties-columns with NHiberante and access those properties one after the other, would it query the database for each property?
Example:
public class Product
{
public virtual int ID {get; set;}
public virtual string Name {get; set;}
public virtual string FullName {get; set;}
public virtual float Price {get; set;}
}
public class ProductMap : ClassMap<Product>
{
Id(p => p.ID);
Map(p => p.Name).Not.LazyLoad();
Map(p => p.FullName).LazyLoad(); // Redundant - I know...
Map(p => p.Price).LazyLoad(); // Redundant - I know...
}
if I query the DB like this:
var product = session.Load<Prodct>(2);
if (product.FullName == "*" && product.Price = 111)
Will there be 3 queries
The Product entity
The FullName property
The Price property
or when NHibernate query the DB for FullName it will query all the columns of the row?
NHibernate will load all the lazy properties of an entity in a single query (you can try for yourself...)
The main use case for this feature is blobs.
Lazy references, on the other hand, are loaded as needed.
As a side note, session.Load does not query the DB; it just creates a proxy, which will be loaded lazily. Use session.Get.
there will be 2 queries
The Product entity
All LazyLoaded Properties
Related
Using ASP Core 2 with EF Core and SQL Server. I have, what I think is a straightforward task of retrieving a list of manufacturers (or individual manufacturer) for a given distributor.
The Users table provides the authenticated user and each is associated with one distributor (and represented in the model as _user). So, when the action GetManufacturers() is called on the ManufacturersController, it should return all manufacturers for the given distributor. Likewise GetManufacturers(int id) should return a single manufacturer iff it is associated with the authenticated distributor.
To do this I'm trying various formulations like:
await _context.Manufacturers
.Include(a => a.Addresses)
.Include(m => m.DistributorManufacturers)
.Where (a => a.AddressesNavigation.State = "CA")
.Where (m => m.Id == id) // Manufacturers Id
.Where (d => d.DistributorManufacturers.DistributorId == _user.DistributorId)
.AsNoTracking()
.ToListAsyc()
VS is complaining that ICollection<DistributorManufacturers> does not contain a definition for DistributorId (even though I copied/pasted it from the class). It is not conceptually different from my filter on Addresses.
I've also tried .ThenInclude to add the Distributors table but no luck.
The DistributorManufacturers table was created with Scaffold-DbContext and has the foreign keys and navigation properties defined.
So, did some work to re-create your models. The only thing I changed was I added the userId in the Distributor table instead of the opposite. This will be a long answer.. so hang on
Models (omitted User and Address entities because there's nothing special with them)
public abstract class Entity
{
public int Id { get; set; }
}
public class Distributor : Entity
{
public User User { get; set; }
public int UserId { get; set; }
public Address Address { get; set; }
public int AddressId { get; set; }
public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}
public class Manufacturer : Entity
{
public Address Address { get; set; }
public int AddressId { get; set; }
public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}
public class DistributorManufacturer
{
public Distributor Distributor { get; set; }
public int DistributorId { get; set; }
public Manufacturer Manufacturer { get; set; }
public int ManufacturerId { get; set; }
}
Configured like this:
modelBuilder.Entity<Distributor>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UserId);
modelBuilder.Entity<Distributor>()
.HasOne(p => p.Address)
.WithMany()
.HasForeignKey(p => p.AddressId);
modelBuilder.Entity<Manufacturer>()
.HasOne(p => p.Address)
.WithMany()
.HasForeignKey(p => p.AddressId);
// many to many mapping
modelBuilder.Entity<DistributorManufacturer>()
.HasKey(bc => new { bc.DistributorId, bc.ManufacturerId });
modelBuilder.Entity<DistributorManufacturer>()
.HasOne(bc => bc.Distributor)
.WithMany(b => b.DistributorManufacturers)
.HasForeignKey(bc => bc.DistributorId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DistributorManufacturer>()
.HasOne(bc => bc.Manufacturer)
.WithMany(c => c.DistributorManufacturers)
.HasForeignKey(bc => bc.ManufacturerId)
.OnDelete(DeleteBehavior.Restrict);
Inserted this values:
select * from Users
select * from Distributors
select * from Manufacturers
select * from DistributorManufacturers
Then, in the GetManufacturers() action you wanted to return all Manufacturers for the logged in Distributor, AKA User. (This is my assumption from your question.. correct me if I'm wrong). So, down to the query:
// Simulate getting the Id of the logged in User.
var userId = 1;
var query = (from m in _context.Manufacturers
join dm in _context.DistributorManufacturers on m.Id equals dm.ManufacturerId
join dist in _context.Distributors on dm.DistributorId equals dist.Id
join adrs in _context.Addresses on m.AddressId equals adrs.Id
where dist.UserId == userId
select new
{
ManufacturerId = m.Id,
ManufacturerName = m.Name,
DistributorId = dist.Id,
DistributorName = dist.Name,
Address = adrs
}).ToList();
Resulting in this:
[
{
"manufacturerId": 1,
"manufacturerName": "Manufacturer 1",
"distributorId": 1,
"distributorName": "Distributor 1",
"address": {
"street": "Street 1",
"city": "New York",
"state": "NY",
"id": 1
}
},
{
"manufacturerId": 2,
"manufacturerName": "Manufacturer 2",
"distributorId": 1,
"distributorName": "Distributor 1",
"address": {
"street": "Street 2",
"city": "New York",
"state": "NY",
"id": 2
}
}
]
To get the GetManufacturers(int id) working, just add the Manufacturer Id to the where clause. Since it's doing a inner join on DistributorManufacturer, if there's no relationship with the logged in user it will return null.
Note: In EF Core, when you have a many-to-many relationship, you need (for now at least..) to have the joint table as an entity. You can check the discussion about this here: https://github.com/aspnet/EntityFrameworkCore/issues/1368
You can query with foreign table data like:
_context.MainTable
.Include(i=>i.ForeignTable)
.Where(w=>w.ForeignTable
.Where(wh=>wh.ForeignId==userInput).Count()>0)
.ToList();
Your query thus can be:
await _context.Manufacturers
.Include(a => a.Addresses)
.Include(m => m.DistributorManufacturers)
.Where (a => a.AddressesNavigation.State = "CA")
.Where (m => m.Id == id)
.Where (d => d.DistributorManufacturers
.Where(w=>w.DistributorId == _user.DistributorId).Count()>0)
.AsNoTracking()
.ToListAsnyc()
It seems to me you wanted to configure a many-to-many relationship between Distributors and Manufacturers: Every Distributor has zero or more Manufacturers, every Manufacturer delivers to zero or more Distributors.
If you'd configured this many-to-many relationship according to the entity framework code first many-to-many conventions, you would have something like:
class Distributor
{
public int Id {get; set;}
public string Name {get; set;}
// a distributor has exactly one Address using foreign key:
public int AddressId {get; set;}
public Address Address {get; set;}
// a Distributor has zero or more Manufacturers: (many-to-many)
public virtual ICollection<Manufacturer> Manufacturers {get; set;}
// a Distirbutor has zero or more Users: (one-to-many)
public virtual ICollection<User> Users {get; set;}
}
class Manufacturer
{
public int Id {get; set;}
public string Name {get; set;}
// a Manufacturer has exactly one Address using foreign key:
public int AddressId {get; set;}
public Address Address {get; set;}
// a Manufacturer has zero or more Distributors (many-to-many)
public virtual ICollection<Distributor> Distributors {get; set;}
}
There is also a User: every User belongs to exactly one Distributor
class User
{
public int Id {get; set;}
// a user belongs to exactly one Distributor, using foreign key:
public int DistributorId {get; set;}
public virtual Distributor Distributor {get; set;}
...
}
Finally the DbContext
class MyDbContext : DbContext
{
public DbSet<Distributor> Distributors {get; set;}
public DbSet<Manufacturer> Manufacturers {get; set;}
public DbSet<User> Users {get; set;}
public DbSet<Address> Addresses {get; set;}
}
The above is all that entity framework needs to know to understand that you want a many-to-many between Distributors and ManuFacturers. Entity Framework will created a proper junction table for you, although you won't need it in your queries as I'll show you below. If you don't like the default junction table that entity framework creates for you, you can use fluent API to define the table and column names:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Distributor>()
.HasMany(distributor=> distributor.Manufacturers)
.WithMany(manufacturer => manufacturer.Distributors)
.Map(map =>
{
map.MapLeftKey("DistributorId");
map.MapRightKey("ManufacturerId");
map.ToTable("DistributorsManufacturers");
});
Although internally entity framework will use the junction table, you won't use it in your queries, just use the ICollections:
I've got a _user and I want several properties of the zero or one
Distributor of this user, together with several properties of all the
Manufacturers of this Distributor
Although the Include statement can be used, it is seldom wise to do so. One of the slower parts of database queries is the transfer of the selected data to your process, so you should limit the amount of transferred data to only the properties you really plan to use. Include will transfer all properties, and I highly doubt whether you'll use them, especially all the foreign keys with all the same values.
So your query using the ICollection:
var _user = ... // I've got a User
var result = dbContext.Distributers
.Where(distributor => distributor.Id == _user.DistributorId)
.Select(distributor => new
{
// select only the distributor properties you plan to use
Id = distributor.Id,
Name = distributor.Name,
Address = new
{
// again: only the properties you plan to use
Street = distributor.Address.Street,
City = distributor.Address.City,
Zip = distributor.Address.Zip,
}),
// fetch (all or some) manufacturers of this distributor
Manufacturers = distributor.Manufacturers
.Where(manufacturer => manufacturer.Address.NavigationState == "CA")
.Select(manufacturer => new
{
// select only the properties you plan to use
// probably not the foreign key to the junction table
Name = manufacturer .Name,
Address = new {...},
...
})
.ToList(),
})
.SingleOrDefault();
It might be that you want some different properties, but you get the gist
From what I've read elsewhere, this is not do-able in the format that you are attempting. I still believe I've seen it done, but I didn't like what I saw and now don't recall the details.
My suggestion is that you turn your query around so that you are querying the Distributor (as specified by _user.DistributorId and then .Include() the Manufacturers.
However... I think you'll run into the same problem when querying the AddressesNavigation.State. As you say it is not conceptually different. You may only believe this is working due to seeing an error relating to the condition which comes later in your code - but that's no guarantee of the application of the conditions in the compiled query expression.
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.
I would like to do a LINQ query that returns all of the records for the Parent table, and includes the Child, if applicable. I know I can do a LINQ join with DefaultIfEmpty(), but then I must output a ViewModel. I'd like to get an IQuerable<Parent> of the actual Parent Class.
So, if I have these classes:
public class Parent
{
[Key]
public int ParentId {get; set;}
public string ParentName {get; set;}
public int? MyChildId {get; set;}
[ForeignKey("MyChildId")]
public virtual Child MyChild {get; set;}
public bool IsActive {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public string ChildName {get;set;}
}
In LINQPad, if I do this:
var results = db.Parent.Where(ra => ra.IsActive);
results.Dump();
I get 111 records.
If I do this:
var results = db.Parent.Where(ra => ra.IsActive);
var results2 = (from r in results
select new
{
ParentId = r.ParentId,
ParentName = r.ParentName,
MyChildId = r.MyChildId
});
results2.Dump();
I also receive 111 records.
But if I do this:
var results = db.Parent.Where(ra => ra.IsActive);
var results2 = (from r in results
select new
{
ParentId = r.ParentId,
ParentName = r.ParentName,
MyChildId = r.MyChildId,
IsActive = r.IsActive,
MyChildName = r.MyChild == null ? null : r.MyChild.ChildName
});
results2.Dump();
I only get 50 records. These are the 50 Parent records that have a child. If they do not have a child, they don't come back.
The SQL generated looks like this:
SELECT
[Extent1].[ParentId] AS [ParentId],
[Extent1].[ParentName] AS [ParentName],
[Extent1].[IsActive] AS [IsActive],
[Extent2].[ChildName] AS [ChildName]
FROM [dbo].[Parent] AS [Extent1]
INNER JOIN [dbo].[Child] AS [Extent2] ON [Extent1].[MyChildId] = [Extent2].[ChildId]
WHERE [Extent1].[IsActive] = 1
How can I get a resultset that includes all 111 Parent records, even if they have no child, but does include the Child elements, if they are there?
UPDATE
So, I may have lied a bit. I posted the above for simplicity sake, but just in case it helps, here is a closer sample of what the code does:
public class Parent
{
[Key]
public int ParentId {get; set;}
public string ParentName {get; set;}
public int? MyChildId {get; set;}
[ForeignKey("MyChildId")]
public virtual Child MyChild {get; set;}
[ForeignKey("MyChildId")]
public virtual StepChild MyStepChild {get; set;}
public bool IsActive {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public string ChildName {get;set;}
}
public class StepChild
{
public int StepChildId {get;set;}
public string StepChildName {get;set;}
}
Sometimes it's hard to tell what's going on behind the scenes of EF, and you can encounter some non-obvious behaviors, what I usually do in such cases - is check the actually generated SQL query, and tweak the LINQ query until it is logically equivalent to the query I expect.
This is not the best approach as it is dependent on implementation details, that can change, but sometimes it is the only way to overcome a EF bug.
You can use ObjectQuery or EF logging and interception of DB calls to get to actual SQL query
Your foreign key is non nullable by default (or required), so you need to tell the EF that it should be optional. To make it you need to override the following modelBuilder method:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
and configure your Entity foreign key:
modelBuilder.Entity<Parent>().HasOptional(e => e.MyChild).WithMany();
Details you can look here: http://blog.staticvoid.co.nz/2012/7/17/entity_framework-navigation_property_basics_with_code_first
You need two separate FK properties one int? personAssigneeId and another int? organizationAssigneeId. These FKs are pointing to two completely different entities. EF is not able to work properly if you reuse the same FK for two separate entities, it needs a FK per entity.
I use Entity Framework Code First and I have three tables (for example):
public class Motorbike()
{
public int Id {get; set;}
public string Producent {get; set;}
public Engine Motor {get; set;}
public Tire Tires {get; set;}
}
public class Engine()
{
public int Id {get; set;}
public int Power {get; set;}
public decimal Price {get;set;}
}
public class Tire()
{
public int Id {get; set;}
public int Size {get; set;}
public decimal Price {get; set;}
}
It's just example, in fact it's more complicated.
Entity Frmaework generates table for me, but tables Motorbike has column: Id, Power, Engine_Id (where storing only number - id engine, not whole object) and Tire_Id (where storing only number - id tire, not whole object).
I know how to insert data - just create new Motorbike object, save to his fields data (for Engine and Tire fields I save whole objects not only id) and use .Add() method from my context.
But how to get data for row where motorbike id is (for example) 1?
I've tried something like this:
List<Motorbike> motorbikes= new List<Motorbike>();
var list = _context.Motorbike.Where(p => p.Id == 1);
motorbikes.AddRange(list);
but always I've got null for Engine and Tire fields (fields Id and Producent are fill properly).
Use Include to load related entities like:
var list = _context.Motorbike
.Include(m=> m.Engine)
.Include(m=> m.Tire)
.Where(p => p.Id == 1);
See: Entity Framework - Loading Related Entities
You're looking for the Include() method.
List<Motorbike> motorbikes = _context.Motorbike
.Include(p => p.Engine)
.Include(p => p.Tire)
.Where(p => p.Id == 1)
.ToList();
I have a structure here I have a ManyToManyToMany relationship.
This is my (truncated) fluent mappings.
public AgencyMap()
{
Id(x => x.AgencyId, "AgencyId");
Map(x => x.AgencyCode, "AgencyCode");
HasManyToMany<Personnel>(x => x.Personnel)
.WithTableName("AgencyPersonnel")
.WithParentKeyColumn("AgencyId")
.WithChildKeyColumn("PersonnelId").Cascade.All().LazyLoad();
}
public PersonnelMap()
{
Id(x => x.PersonnelId, "PersonnelId");
HasManyToMany<Discipline>(x => x.Disciplines)
.WithTableName("AgencyPersonnelDiscipline")
.WithParentKeyColumn("AgencyPersonnelId")
.WithChildKeyColumn("DisciplineId")
.Cascade.SaveUpdate().LazyLoad();
}
public DisciplineMap()
{
SchemaIs("dbo");
Id(x => x.DisciplineId, "DisciplineId");
Map(x => x.DisciplineCode, "DisciplineCode");
Map(x => x.Description, "Description");
}
If I then run code like this.
Agency agency = m_AgencyRepository.Get(10);
var personnel = new Personnel() { ... };
personnel.Disciplines.Add(new Discipline() { ... });
agency.Personnel.Add(personnel);
m_AgencyRepository.Save(agency);
When I run this code I get this error.
could not insert collection: [PPSS.Model.Personnel.Disciplines#22][SQL: SQL not available]
The INSERT statement conflicted with the FOREIGN KEY constraint "AgencyPersonnel_AgencyPersonnelDiscipline_FK1". The conflict occurred in database "PPSS2", table "dbo.AgencyPersonnel", column 'AgencyPersonnelId'.\r\nThe statement has been terminated.
Enitities look like.
public class Agency
{
public virtual int AgencyId {get; set;}
public virtual IList<Personnel> Personnel {get; set;}
}
public class Personnel
{
public virtual int PersonnelId {get; set;}
public virtual IList<Agency> Agencies {get; set;}
public virtual IList<Dependency> Dependencies {get; set;}
}
public class Dependency
{
public virtual int DependencyId {get; set;}
public virtual string Name {get; set;}
}
If I add an inverse to the ManyToMany in personnel then the Personnel is saved but not the disciplines (no exception is raised).
How should this mapping be done?
Edit:
I have got some more info. If I disable the constraint AgencyPersonnel_AgencyPersonnelDiscipline_FK1 then it all is inserted ok. This makes me think it is the order nHibernate is inserting that is the problem. What I would expect it to do in order is.
INSERT INTO Personnel
Get last key (PersonnelId)
INSERT INTO AgencyPersonnelDiscipline with AgencyId and PersonnelId.
This would not violate any constraints.
Since this is a truncated mapping I'm gonna take a shot in the dark :). This kind on of exception usually happens to me when I have 2 entities : X and Y , and I map them from both directions, something like :
X has a property Y
Y has a list of X
And none of them has the Inverse() attribute set. Check if you're mapping something in both directions but not setting one on them Inverse().
Given that the maps you have are correct for the types you state, you haven't mapped back from Personnel to Agency or from Discipline to Personnel.