I have a class Delivery
, and another class Employee
. I created a property to return the user name (I am going to load data into the data grid view, it is convenient for me to create an additional property with a name to specify it instead of the employee ID), but the studio hangs and tells me that the Employee
property is null.
I connected the necessary function in the configurator, and in theory it should work, but it does not. I use SQLite.
Supply
class:
public partial class Supply : EntityBase
{
[Browsable(false)]
public override int Id { get; set; }
[DisplayName("Дата поставки")]
public string Date { get; set; } = null!;
[Browsable(false)]
public int EmployeeId { get; set; }
[Browsable(false)]
public virtual Employee Employee { get; set; } = null!;
[Browsable(false)]
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
[NotMapped]
[DisplayName("Работник")]
public string EmployeeName
{
get
{
using(var context = new IssuingOrdersDbContext())
{
var item = context.Supplies.First(i=>i.Id==Id);
return item.Employee.FullName;
}
}
}
}
Configuration:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlite("Data Source=D:\\СГАЭК\\БДиСУБД\\Сем2\\Курсовая\\IssuingOrdersDB.db");
I have a class Delivery
, and another class Employee
. I created a property to return the user name (I am going to load data into the data grid view, it is convenient for me to create an additional property with a name to specify it instead of the employee ID), but the studio hangs and tells me that the Employee
property is null.
I connected the necessary function in the configurator, and in theory it should work, but it does not. I use SQLite.
Supply
class:
public partial class Supply : EntityBase
{
[Browsable(false)]
public override int Id { get; set; }
[DisplayName("Дата поставки")]
public string Date { get; set; } = null!;
[Browsable(false)]
public int EmployeeId { get; set; }
[Browsable(false)]
public virtual Employee Employee { get; set; } = null!;
[Browsable(false)]
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
[NotMapped]
[DisplayName("Работник")]
public string EmployeeName
{
get
{
using(var context = new IssuingOrdersDbContext())
{
var item = context.Supplies.First(i=>i.Id==Id);
return item.Employee.FullName;
}
}
}
}
Configuration:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlite("Data Source=D:\\СГАЭК\\БДиСУБД\\Сем2\\Курсовая\\IssuingOrdersDB.db");
Share
Improve this question
edited Mar 12 at 18:22
marc_s
756k184 gold badges1.4k silver badges1.5k bronze badges
asked Mar 12 at 18:04
Quasarka N.Quasarka N.
11 bronze badge
1
|
1 Answer
Reset to default 0While it isn't clear why your example doesn't appear to work, it does outline a few serious flaws in your approach. With lazy loading enabled, when you fetched your initial Supply that you want to get the Employee name from, you should have been able to get the Employee full name simply by accessing: supply.Employee.Fullname
. This would be enough to lazy load the Employee on access if not already loaded, rather than calling a supply.EmployeeName
looking to load the name.
The caveat that catches people out about lazy loading is that it can only occur so long as the DbContext
that read the initial entity (Supply) is still in scope. If the DbContext
was disposed or the entity was detached, lazy loading cannot occur.
It is generally bad practice to have an entity create or access a DbContext scope to load or update data. However, a more serious problem would be how you also rely on lazy loading. Taking the following code:
[NotMapped]
[DisplayName("Работник")]
public string EmployeeName
{
get
{
using(var context = new IssuingOrdersDbContext())
{
var item = context.Supplies.First(i=>i.Id==Id);
return item.Employee.FullName;
}
}
}
If this were to work, you are effectively triggering two SQL statements to read the entirety of the Supply and Employee just to get the name.
SELECT TOP 1 * FROM Supplies WHERE Id = %p0
SELECT * FROM Employees WHERE Id = %p1 (EmployeeId returned from first statement)
As a worst case scenario where you must go back to the database:
[NotMapped]
[DisplayName("Работник")]
public string EmployeeName
{
get
{
using(var context = new IssuingOrdersDbContext())
{
return context.Supplies
.AsNoTracking()
.Where(i => i.Id == Id)
.Select(i => i.Employee.FullName)
.First();
}
}
}
This generates a single, fast SQL query to fetch just the employee name for the current supply:
SELECT TOP 1 e.FullName FROM Supplies s
INNER JOIN Employees e ON e.Id = s.EmployeeId
WHERE s.Id = %p0
Better would be to not rely on entities for additional view/logic concerns and instead project whatever data you need to display to view models. Things like [DisplayName]
are view concerns, not data concerns. Entities should only cover data concerns leaving view concerns to the view model. For instance:
// Entity: Only data concerns here.
public partial class Supply : EntityBase
{
public override int Id { get; set; }
public string Date { get; set; } = null!;
public int EmployeeId { get; set; }
public virtual Employee Employee { get; set; } = null!;
public virtual ICollection<Product> Products { get; } = [];
}
// ViewModel: View concerns, only a simple POCO with properties
// but can have view-specific logic, no interaction with DbContext.
public class SupplySummaryViewModel
{
[Browsable(false)]
public override int Id { get; set; }
[DisplayName("Дата поставки")]
public string Date { get; set; }
[Browsable(false)]
public int EmployeeId { get; set; }
[DisplayName("Работник")]
public string EmployeeName { get; set; }
}
Then when you want to display your UI with supplies:
using (var context = new IssuingOrdersDbContext())
{
var supplies = context.Supplies
.AsNoTracking()
.Select(i => new SupplySummaryViewModel
{
Id = i.Id,
Date = i.Date,
EmployeeId = i.EmployeeId,
EmployeeName = i.Employee.FullName
}).ToList();
return View(supplies);
}
This can be simplified with a mapper like Automapper or Mapperly etc. which can replace the need to use Select()
to populate the view model with a tidier .ProjectTo<SupplySummaryViewModel>(config)
for example (Automapper) where "config" is a mapper configuration set up with any rules about how to populate the VM from an entity.
The benefit over loading entities and especially over counting on lazy loading is that this generates a single SQL statement that automatically reads the necessary tables, and only reads the columns needed for the view model. (instead of all columns from the Supply and Employee records)
Delivery
and anEmployee
class - and then show us aSupply
class...... what exactly are you expecting us to be able to tell you?!?! Please put a bit more effort into making your question understandable to folks who cannot see your screen and your entire code base .... – marc_s Commented Mar 12 at 18:23