Projection refers to the operation of transforming an object into a new form that often consists only of those properties that will be subsequently used. By using projection, you can construct a new type that is built from each object. You can project a property and perform a mathematical function on it. You can also project the original object without changing it.
Microsoft Docs which uses the LINQ syntax rather than Lambda syntax.
The project is based off the model shown below in the last code block. For those just starting out with C#, an Expression and Func are intermediate to advance topics which will be taught in a class before this class or after this class.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using SqlOperationsEntityFrameworkCore.Models;
namespace SqlOperationsEntityFrameworkCore.Models
{
/// <summary>
/// Purpose of this class is to return only data needed rather
/// than bring in all properties e.g. without using projections
/// Categories and Supplier properties would be loaded.
/// </summary>
public class Product : INotifyPropertyChanged
{
private string _productName;
private short? _unitsInStock;
private DateTime? _discontinuedDate;
private string _categoryName;
private int? _categoryId;
private short? _reorderLevel;
private short? _unitsOnOrder;
private decimal? _unitPrice;
private string _quantityPerUnit;
private string _supplierName;
private int? _supplierId;
private int _productId;
/// <summary>
/// Primary key
/// </summary>
public int ProductId
{
get => _productId;
set
{
_productId = value;
OnPropertyChanged();
}
}
public string ProductName
{
get => _productName;
set
{
_productName = value;
OnPropertyChanged();
}
}
/// <summary>
/// Primary key to supplier
/// </summary>
public int? SupplierId
{
get => _supplierId;
set
{
_supplierId = value;
OnPropertyChanged();
}
}
public string SupplierName
{
get => _supplierName;
set
{
_supplierName = value;
OnPropertyChanged();
}
}
public string QuantityPerUnit
{
get => _quantityPerUnit;
set
{
_quantityPerUnit = value;
OnPropertyChanged();
}
}
public decimal? UnitPrice
{
get => _unitPrice;
set
{
_unitPrice = value;
OnPropertyChanged();
}
}
public short? UnitsInStock
{
get => _unitsInStock;
set
{
_unitsInStock = value;
OnPropertyChanged();
}
}
public short? UnitsOnOrder
{
get => _unitsOnOrder;
set
{
_unitsOnOrder = value;
OnPropertyChanged();
}
}
public short? ReorderLevel
{
get => _reorderLevel;
set
{
_reorderLevel = value;
OnPropertyChanged();
}
}
/// <summary>
/// Category identifier for product
/// </summary>
public int? CategoryId
{
get => _categoryId;
set
{
_categoryId = value;
OnPropertyChanged();
}
}
public string CategoryName
{
get => _categoryName;
set
{
_categoryName = value;
OnPropertyChanged();
}
}
public DateTime? DiscontinuedDate
{
get => _discontinuedDate;
set
{
_discontinuedDate = value;
OnPropertyChanged();
}
}
public override string ToString() => ProductName;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static Expression <Func<Products, Product>> Projection =>
product => new Product()
{
ProductId = product.ProductId,
ProductName = product.ProductName,
SupplierName = product.Supplier.CompanyName,
SupplierId = product.SupplierId,
QuantityPerUnit = product.QuantityPerUnit,
ReorderLevel = product.ReorderLevel,
UnitPrice = product.UnitPrice,
UnitsInStock = product.UnitsInStock,
UnitsOnOrder = product.UnitsOnOrder,
CategoryId = product.CategoryId,
CategoryName = product.Category.CategoryName,
DiscontinuedDate = product.DiscontinuedDate
};
}
}
A projection done properly provides a sub-set of properties for a model. Another way is Dependency Injection using a library like AutoMapper.
public static async Task<List<Product>> GetProductsWithProjection()
{
var productList = new List<Product>();
await Task.Run(async () =>
{
await using var context = new NorthwindContext();
productList = await context.Products
.Include(product => product.Supplier)
.Select(Product.Projection )
.ToListAsync();
});
return productList;
}
The project is based off the model shown below in the last code block. For those just starting out with C#, an Expression and Func are intermediate to advance topics which will be taught in a class before this class or after this class.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using SqlOperationsEntityFrameworkCore.Models;
namespace SqlOperationsEntityFrameworkCore.Models
{
/// <summary>
/// Purpose of this class is to return only data needed rather
/// than bring in all properties e.g. without using projections
/// Categories and Supplier properties would be loaded.
/// </summary>
public class Product : INotifyPropertyChanged
{
private string _productName;
private short? _unitsInStock;
private DateTime? _discontinuedDate;
private string _categoryName;
private int? _categoryId;
private short? _reorderLevel;
private short? _unitsOnOrder;
private decimal? _unitPrice;
private string _quantityPerUnit;
private string _supplierName;
private int? _supplierId;
private int _productId;
/// <summary>
/// Primary key
/// </summary>
public int ProductId
{
get => _productId;
set
{
_productId = value;
OnPropertyChanged();
}
}
public string ProductName
{
get => _productName;
set
{
_productName = value;
OnPropertyChanged();
}
}
/// <summary>
/// Primary key to supplier
/// </summary>
public int? SupplierId
{
get => _supplierId;
set
{
_supplierId = value;
OnPropertyChanged();
}
}
public string SupplierName
{
get => _supplierName;
set
{
_supplierName = value;
OnPropertyChanged();
}
}
public string QuantityPerUnit
{
get => _quantityPerUnit;
set
{
_quantityPerUnit = value;
OnPropertyChanged();
}
}
public decimal? UnitPrice
{
get => _unitPrice;
set
{
_unitPrice = value;
OnPropertyChanged();
}
}
public short? UnitsInStock
{
get => _unitsInStock;
set
{
_unitsInStock = value;
OnPropertyChanged();
}
}
public short? UnitsOnOrder
{
get => _unitsOnOrder;
set
{
_unitsOnOrder = value;
OnPropertyChanged();
}
}
public short? ReorderLevel
{
get => _reorderLevel;
set
{
_reorderLevel = value;
OnPropertyChanged();
}
}
/// <summary>
/// Category identifier for product
/// </summary>
public int? CategoryId
{
get => _categoryId;
set
{
_categoryId = value;
OnPropertyChanged();
}
}
public string CategoryName
{
get => _categoryName;
set
{
_categoryName = value;
OnPropertyChanged();
}
}
public DateTime? DiscontinuedDate
{
get => _discontinuedDate;
set
{
_discontinuedDate = value;
OnPropertyChanged();
}
}
public override string ToString() => ProductName;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static Expression <Func<Products, Product>> Projection =>
product => new Product()
{
ProductId = product.ProductId,
ProductName = product.ProductName,
SupplierName = product.Supplier.CompanyName,
SupplierId = product.SupplierId,
QuantityPerUnit = product.QuantityPerUnit,
ReorderLevel = product.ReorderLevel,
UnitPrice = product.UnitPrice,
UnitsInStock = product.UnitsInStock,
UnitsOnOrder = product.UnitsOnOrder,
CategoryId = product.CategoryId,
CategoryName = product.Category.CategoryName,
DiscontinuedDate = product.DiscontinuedDate
};
}
}
A model represents a table in a database table which may or may not have navigation properties
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
#nullable disable
namespace SqlOperationsEntityFrameworkCore.Models
{
/// <summary>
/// Class which represents products in NorthWind database.
/// </summary>
/// <remarks>
/// Code was generated by EF Power Tools, Karen added INotifyPropertyChanged
/// </remarks>
public partial class Products
{
public int ProductId { get; set; }
[RegularExpression("^.{3,}$", ErrorMessage = "{0} Minimum 3 characters required")]
[Required(ErrorMessage = "{0} Required")]
[StringLength(40, MinimumLength = 3, ErrorMessage = "Invalid {0}")]
public string ProductName { get; set; }
public int? SupplierId { get; set; }
public int? CategoryId { get; set; }
public string QuantityPerUnit { get; set; }
public decimal? UnitPrice { get; set; }
public short? UnitsInStock { get; set; }
public short? UnitsOnOrder { get; set; }
public short? ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public DateTime? DiscontinuedDate { get; set; }
public virtual Categories Category { get; set; }
public virtual Suppliers Supplier { get; set; }
}
}