使用Odata基于EF查询DTO

Posted

技术标签:

【中文标题】使用Odata基于EF查询DTO【英文标题】:Querying DTOs based on EF using Odata 【发布时间】:2020-08-17 11:52:17 【问题描述】:

我有一个带有 SQL Server 数据库和 EF 数据模型的 ASP.NET Core Web API 设置。

版本:

EF:Microsoft.EntityFrameworkCore 5.0.0-preview.7.20365.15 OData:Microsoft.AspNetCore.OData 7.4.1 .Net Core 3.1

问题是:我不能使用过滤器,在展开中选择(嵌套展开)。 OData 未将过滤器添加到 SQL Server Profiler 上显示的 SQL 查询的 where 条件的示例 URL:

https://localhost:44327/odata/clientcontract?$expand=ContactsInfo($filter=value eq '100003265')

https://localhost:44327/odata/clientcontract?$expand=Documents($filter=documentnnumber eq '100003265')

这些是我的数据库优先实体模型:

public partial class ClientRef

    public ClientRef()
    
        Addresses = new HashSet<Address>();
        Assets = new HashSet<Asset>();
        ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
        ClientRelationCompClient1Navigations = new HashSet<ClientRelationComp>();
        ClientRelationCompClient2Navigations = new HashSet<ClientRelationComp>();
        Clients = new HashSet<Client>();
        CommentComps = new HashSet<CommentComp>();
        Companies = new HashSet<Company>();
        Documents = new HashSet<Document>();
        PhysicalPeople = new HashSet<PhysicalPerson>();
    

    [Column("Inn")]
    public int Id  get; set; 

    public virtual ICollection<Address> Addresses  get; set; 
    public virtual ICollection<Asset> Assets  get; set; 
    public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps  get; set; 
    public virtual ICollection<ClientRelationComp> ClientRelationCompClient1Navigations  get; set; 
    public virtual ICollection<ClientRelationComp> ClientRelationCompClient2Navigations  get; set; 
    public virtual ICollection<Client> Clients  get; set; 
    public virtual ICollection<CommentComp> CommentComps  get; set; 
    public virtual ICollection<Company> Companies  get; set; 
    public virtual ICollection<Document> Documents  get; set; 
    public virtual ICollection<PhysicalPerson> PhysicalPeople  get; set; 


public partial class Document

    public int Id  get; set; 
    public string DocumentNumber  get; set; 
    public int DocumentType  get; set; 
    public int Inn  get; set; 
    public DateTime ValidFrom  get; set; 
    public DateTime ValidTo  get; set; 
    public DateTime? DocumentExpireDate  get; set; 

    public virtual ClientRef InnNavigation  get; set; 


public partial class ClientContactInfoComp

    public int Id  get; set; 
    public int Inn  get; set; 
    public int ContactInfoId  get; set; 
    public int? Point  get; set; 
    public DateTime ValidFrom  get; set; 
    public DateTime ValidTo  get; set; 

    [ForeignKey("ContactInfoId")]
    public  ContactInfo ContactInfo  get; set; 
    public virtual ClientRef InnNavigation  get; set; 


public partial class ContactInfo

    public ContactInfo()
    
        CallHistories = new HashSet<CallHistory>();
        ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
        CommentComps = new HashSet<CommentComp>();
    

    public int Id  get; set; 
    public int? Type  get; set; 
    public string Value  get; set; 

    public virtual ICollection<CallHistory> CallHistories  get; set; 
    public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps  get; set; 
    public virtual ICollection<CommentComp> CommentComps  get; set; 


public partial class CommentComp

    public int Id  get; set; 
    public int Inn  get; set; 
    public int? CommentId  get; set; 
    public int? ContactId  get; set; 

    public virtual Comment Comment  get; set; 
    public virtual ContactInfo Contact  get; set; 
    public virtual ClientRef InnNavigation  get; set; 


public partial class Comment

    public Comment()
    
        CommentComps = new HashSet<CommentComp>();
    

    public int Id  get; set; 
    public string Text  get; set; 
    public DateTime? CreateTimestamp  get; set; 
    public string Creator  get; set; 

    public virtual ICollection<CommentComp> CommentComps  get; set; 

这些是我的 DTO:

[DataContract]
public class ClientContract

    public ClientContract()
    
        ContactsInfo = new List<ContactInfoContract>();
        Documents = new List<DocumentContract>();
        Relations = new List<RelationContract>();
        ClientComment = new CommentContract();
    

    [DataMember(Name = "INN")]
    [Key]
    public int INN  get; set; 

    [DataMember(Name = "validfrom")]
    public DateTime ValidFrom  get; set; 

    [DataMember(Name = "validto")]
    public DateTime ValidTo  get; set; 

    [DataMember(Name = "clienttype")]
    public ClientType ClientType  get; set; 

    [DataMember(Name = "companyname")]
    public string CompanyName  get; set; 

    [DataMember(Name = "firstname")]
    public string FirstName  get; set; 

    [DataMember(Name = "lastname")]
    public string LastName  get; set; 

    [DataMember(Name = "fathername")]
    public string FatherName  get; set; 

    [DataMember(Name = "pinnumber")]
    public string PinNumber  get; set; 

    [DataMember(Name = "birthdate")]
    public DateTime? BirthDate  get; set; 

    [DataMember(Name = "positioncustom")]
    public string PositionCustom  get; set; 

    [DataMember(Name = "position")]
    public int Position  get; set; 

    [DataMember(Name = "monthlyincome")]
    public decimal MonthlyIncome  get; set; 

    [DataMember(Name = "clientcomment")]
    public CommentContract ClientComment  get; set;         

    [DataMember(Name = "contactsinfo")]
    public List<ContactInfoContract> ContactsInfo  get; set; 
    
    [DataMember(Name = "documents")]
    [ForeignKey("Documents")]
    public List<DocumentContract> Documents  get; set; 

    [DataMember(Name = "relations")]
    public List<RelationContract> Relations  get; set; 


public class DocumentContract

    [Key]
    public int Id  get; set; 

    [DataMember(Name = "documentNumber")]
    public string documentNumber  get; set; 

    [DataMember(Name = "documentType")]
    public int documentType  get; set; 

    [DataMember(Name = "documentexpiredate")]
    public DateTime? documentExpireDate  get; set; 


[DataContract]
public class ContactInfoContract

    public ContactInfoContract()
    
        ContactComment = new CommentContract();
    

    [Key]
    public int Id  get; set; 

    [DataMember(Name = "type")]
    public int Type  get; set; 

    [DataMember(Name = "value")]
    public string Value  get; set; 

    [DataMember(Name = "contactComment")]
    public CommentContract ContactComment  get; set; 


public class CommentContract

    [DataMember(Name = "Text")]
    public string Text  get; set; 

    [DataMember(Name = "creator")]
    public string Creator  get; set; 

在 DTO 中没有关系模型。例如:EF中有一个ClientContactInfoComp模型,连接ClientRefs和ContactInfo模型,而DTO中ClientContract是直接用ContactInfoContract引用的。

Startup.cs 中的模型生成器

private  IEdmModel GetEdmModel()

        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
        builder.EntitySet<DocumentContract>("DocumentContract");
        builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);

        return builder.GetEdmModel();


public class ClientContractController : ControllerBase

    [EnableQuery(MaxExpansionDepth = 10)]
    public IQueryable<ClientContract> Get()
    
        var clientRefs = _context.ClientRefs
                         .Include(x => x.Clients.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
                         .Include(x => x.PhysicalPeople.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
                         .Include(x => x.Companies.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
                         .Include(x => x.Documents.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
                         .Include(x => x.ClientContactInfoComps.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
                         .ThenInclude(x => x.ContactInfo)
                         .Include(x => x.Assets.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now));

        List<ClientContract> contracts = new List<ClientContract>();

        foreach (var clientRef in clientRefs)
        
            ClientContract clientContract = new ClientContract()  INN = clientRef.Id ;

            foreach(var c in clientRef.Clients)
            
                clientContract.ClientType = (ClientType) c.ClientType;
            

            foreach (var pp in clientRef.PhysicalPeople)
            
                clientContract.FirstName = pp.FirstName;
                clientContract.LastName = pp.LastName;
            

            foreach (var comp in clientRef.Companies)
            
                clientContract.CompanyName = comp.CompanyName;
            

            foreach (var doc in clientRef.Documents)
            
                clientContract.Documents.Add(new DocumentContract()
                
                    documentNumber = doc.DocumentNumber,
                    documentExpireDate = doc.DocumentExpireDate,
                    documentType = doc.DocumentType
                    
                );
            

            foreach (var comp in clientRef.ClientContactInfoComps)
            
                clientContract.ContactsInfo.Add(new ContactInfoContract
                
                    Type = comp.ContactInfo.Type.Value,
                    Value = comp.ContactInfo.Value
                );
            

            contracts.Add(clientContract);
        

        return contracts.AsQueryable();
    

【问题讨论】:

【参考方案1】:

是的,我从以下链接获得结果: https://localhost:44327/odata/clientcontract

https://localhost:44327/odata/clientcontract?$filter=Id eq 4

https://localhost:44327/odata/clientcontract?$expand=documents,contactsinfo

Startup.cs



   public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    
        services.AddControllers();//.AddNewtonsoftJson(); ;
        services.AddDbContext<DbContext>(options =>
        options.UseSqlServer("connectionstring"));
        services.AddOData();
    

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
        

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        
            endpoints.MapControllers();
            //endpoints.EnableDependencyInjection();
            endpoints.Expand().Select().Filter().OrderBy().Count().MaxTop(10);
            endpoints.MapODataRoute("odata", "odata", GetEdmModel());
        );

        
    

    private  IEdmModel GetEdmModel()
    
        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
        builder.EntitySet<DocumentContract>("DocumentContract");
        builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);
        
        return builder.GetEdmModel();
    

    

【讨论】:

【参考方案2】:

使用OData时应该使用属性名而不是属性名。

OData 客户端库依赖于它自己的属性OriginalNameAttribute 在服务器发出类/成员名称时获取它们的知识。详情可以在here看到。

当我删除 [DataContract] 属性时它可以工作。

public class ClientContract

    public ClientContract()
    
        ContactsInfo = new List<ContactInfoContract>();
    

    [Key]
    public int INN  get; set; 

    public string CompanyName  get; set; 

    public List<ContactInfoContract> ContactsInfo  get; set; 





public class ContactInfoContract

    [Key]
    public int Id  get; set; 

    [DataMember(Name = "type")]
    public int Type  get; set; 

    [DataMember(Name = "value")]
    public string Value  get; set; 


【讨论】:

以上是关于使用Odata基于EF查询DTO的主要内容,如果未能解决你的问题,请参考以下文章

OData $expand、DTO 和实体框架

对 DTO 的 ASP.NET WebApi OData 支持

如何将OData查询与DTO映射到另一个实体?

如何将针对 DTO 的 OData 查询映射到另一个实体?

如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?

Breeze、OData 和无 EF