英文:
Linq: Selecting specific columns using DTO
问题
<!-- language-all: csharp -->
我知道在手动方式中,我可以选择从数据库中提取哪些列(简化的示例):
db.Products.Where(...).Select(p => new {p.Id, p.Name...})
但是,当我有DTO对象时:
public class ProductDTO {
public int Id { get; set; }
public string Name { get; set; }
public ProductDTO(Product p) {
Id = p.Id;
Name = p.Name;
}
}
和查询:
db.Products.Where(...).Select(p => new ProductDTO(p))
从数据库中提取了所有字段,而不仅仅是Id和Name。这是资源的浪费。
为什么LINQ无法看到DTO中有哪些属性并仅提取它们?
我猜这就是DTO的作用,这样我就不必每次都编写手动查询。
英文:
<!-- language-all: csharp -->
I know that in manual way I can choose what columns are fetched from database (simplified examples):
db.Products.Where(...).Select(p => new {p.Id, p.Name...})
But, when i have DTO object:
public class ProductDTO {
public int Id { get; set; }
public string Name { get; set; }
public ProductDTO(Product p) {
Id = p.Id;
Name = p.Name;
}
}
and query:
db.Products.Where(...).Select(p => new ProductDTO(p))
from database are fetched ALL FIELDS, not only Id and Name. It's waste of resources.
Why linq can't see what properties are in DTO and fetch only them?
I guess that's what the DTO is for, so that I don't have to write a manual query every time.
答案1
得分: 3
在你的情况下,当你创建一个新的 ProductDTO
实例时,不幸的是,LINQ 对 ProductDTO
类的属性没有任何信息。但它知道你的 ProductDTO
等待 Product
对象,因此从数据库中下载完整的实体,就像你告诉它在你的 ProductDTO
构造函数中获取完整的 Product p
一样。
> db.Products.Where(...).Select(p => new ProductDTO(p))
可能的解决方案是在你的 LINQ 查询中明确定义所需属性的映射:
db.Products.Where(...).Select(p => new ProductDTO(p.Id, p.Name))
你还可以使用 AutoMapper 来避免重复编写代码。它提供了 ProjectTo<T>()
扩展方法,应该能帮助你:
db.Products.Where(...).ProjectTo<ProductDTO>()
注意:确保你已经正确配置了映射:
Mapper.Initialize(cfg => {
cfg.CreateMap<Product, ProductDTO>();
});
英文:
In your case, when you create a new instance of ProductDTO
, unfortunatelly LINQ doesn't have any information about the properties of the ProductDTO
class. But it knows that your ProductDTO
waits for Product
objecy, and therefore downloads the full entity from the database, as you tell to get the FULL Product p
into your ProductDTO
constructor.
> db.Products.Where(...).Select(p => new ProductDTO(p))
The possible solution is to define the mapping of needed properties EXPLICITLY in your LINQ query:
db.Products.Where(...).Select(p => new ProductDTO(p.Id, p.Name))
You could also use AutoMapper for that to avoid code repeating. It gives you access to ProjectTo<T>()
extension method that should help you:
db.Products.Where(...).ProjectTo<ProductDTO>()
Note: make sure you configured the mapping properly:
Mapper.Initialize(cfg => {
cfg.CreateMap<Product, ProductDTO>();
});
答案2
得分: 1
以下是翻译好的部分:
如果Products和ProductsDTO具有相同的属性名称和类型,您可以使用一个简单的映射器。
这是一个可以自动构建表达式树以执行映射的方法示例:
public static Expression<Func<TSource, TResult>> BuildAutoProjection<TSource, TResult>()
{
var srcparam = Expression.Parameter(typeof(TSource), "src");
var createObject = Expression.New(typeof(TResult));
var propmaps = typeof(TSource).GetProperties()
.Select(p => (p, typeof(TResult).GetProperty(p.Name)))
.Where(x => x.Item2 is not null)
.Select(x => Expression.Bind(x.Item2, Expression.Property(srcparam, x.Item1.Name)));
var InitializePropertiesOnObject = Expression.MemberInit(createObject, propmaps.ToArray());
return Expression.Lambda<Func<TSource, TResult>>(InitializePropertiesOnObject, srcparam);
}
然后,您可以像这样使用它:
db.Products.Where(...).Select(BuildAutoProjection<Products, ProductDTO>());
这种方法不需要 AutoMapper,并且只生成一个SELECT语句,其中包含ProductDTO类型中存在的列。
英文:
If the Products and ProductsDTO have identical property names and types, you can use a simple mapper.
Here's an example of a method that can automatically build an expression tree to perform the mapping:
public static Expression<Func<TSource, TResult>> BuildAutoProjection<TSource, TResult>()
{
var srcparam = Expression.Parameter(typeof(TSource), "src");
var createObject = Expression.New(typeof(TResult));
var propmaps = typeof(TSource).GetProperties().
Select(p => (p, typeof(TResult).GetProperty(p.Name)))
.Where(x => x.Item2 is not null)
.Select(x => Expression.Bind(x.Item2, Expression.Property(srcparam, x.Item1.Name)));
var InitializePropertiesOnObject = Expression.MemberInit(createObject, propmaps.ToArray());
return Expression.Lambda<Func<TSource, TResult>>(InitializePropertiesOnObject, srcparam);
}
You can then use it like this:
db.Products.Where(...).Select(BuildAutoProjection<Products, ProductDTO>());
This method does not require AutoMapper and generates a SELECT statement only with columns that exist in the ProductDTO type.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论