Linq: 使用 DTO 选择特定列

huangapple go评论134阅读模式
英文:

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 =&gt; 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 =&gt; 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 =&gt; new ProductDTO(p.Id, p.Name))

你还可以使用 AutoMapper 来避免重复编写代码。它提供了 ProjectTo&lt;T&gt;() 扩展方法,应该能帮助你:

db.Products.Where(...).ProjectTo&lt;ProductDTO&gt;()

注意:确保你已经正确配置了映射:

Mapper.Initialize(cfg =&gt; {
    cfg.CreateMap&lt;Product, ProductDTO&gt;();
});
英文:

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 =&gt; new ProductDTO(p.Id, p.Name))

You could also use AutoMapper for that to avoid code repeating. It gives you access to ProjectTo&lt;T&gt;() extension method that should help you:

db.Products.Where(...).ProjectTo&lt;ProductDTO&gt;()

Note: make sure you configured the mapping properly:

Mapper.Initialize(cfg =&gt; {
    cfg.CreateMap&lt;Product, ProductDTO&gt;();
});

答案2

得分: 1

以下是翻译好的部分:

如果ProductsProductsDTO具有相同的属性名称和类型,您可以使用一个简单的映射器。

这是一个可以自动构建表达式树以执行映射的方法示例:

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&lt;Func&lt;TSource, TResult&gt;&gt; BuildAutoProjection&lt;TSource, TResult&gt;()
    {
        var srcparam = Expression.Parameter(typeof(TSource), &quot;src&quot;);
        var createObject = Expression.New(typeof(TResult));
        var propmaps = typeof(TSource).GetProperties().
            Select(p =&gt; (p, typeof(TResult).GetProperty(p.Name)))
            .Where(x =&gt; x.Item2 is not null)
            .Select(x =&gt; Expression.Bind(x.Item2, Expression.Property(srcparam, x.Item1.Name)));
        var InitializePropertiesOnObject = Expression.MemberInit(createObject, propmaps.ToArray());

        return Expression.Lambda&lt;Func&lt;TSource, TResult&gt;&gt;(InitializePropertiesOnObject, srcparam);
    }

You can then use it like this:

db.Products.Where(...).Select(BuildAutoProjection&lt;Products, ProductDTO&gt;());

This method does not require AutoMapper and generates a SELECT statement only with columns that exist in the ProductDTO type.

huangapple
  • 本文由 发表于 2023年2月26日 20:42:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75572050.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定