使用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的主要内容,如果未能解决你的问题,请参考以下文章
对 DTO 的 ASP.NET WebApi OData 支持