[英]Entity Framework + ODATA + Dynamic column mapping
我有兩個 OData 控制器:
PersonsController
PersonsUnrestrictedController
它們不同的唯一方式是,根據 controller,一些屬性必須從 person 表中的不同列中獲取它們的值。
PersonsController 將發回一個 Person 列表,其中 Person 的名字、Familyname 等是別名,而 PersonsUnrestrictedController 將發回一個帶有這些人的真實姓名的 Person 列表。 所有其他屬性將完全相同,包括導航屬性及其與其他表的關系。
PersonsController 在任何情況下都不能透露一個人的真實姓名,這一點非常重要。
是否可以在以下之間動態切換:
[Column("AltGivenName")]
public string GivenName { get; set; }
和
[Column("GivenName")]
public string GivenName { get; set; }
取決於 controller?
或者有兩個屬性 GivenName 和 AltGivenName 並根據 controller 動態隱藏/顯示其中 1 個屬性:
[DataMember(Name="GivenName")] //Either should this one be ignored
public string AltGivenName { get; set; }
public string GivenName { get; set; } //or this one, depending on controller
或者還有其他可能的解決方法嗎?
編輯:添加了我的代碼
啟動.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PersonContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("MyConnection")); });
services.AddOData();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
endpoints.Select().Expand().MaxTop(null).Count();
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var persons = builder.EntitySet<Person>("Persons");
return builder.GetEdmModel();
}
}
PersonContext.cs
public class PersonContext : DbContext
{
public DbSet<DbPerson> Persons { get; set; }
public PersonContext(DbContextOptions<PersonContext> options) : base(options)
{
}
}
數據庫人.cs
[Table("Person")]
public class DbPerson
{
[Key]
public int Id { get; set; }
public string GivenName { get; set; }
public string AltGivenName { get; set; }
}
個人.cs
public class Person
{
[Key]
public int Id { get; set; }
public string GivenName { get; set; }
}
MappingHelper.cs
public static class MappingHelper
{
public static Person ToPerson(this DbPerson dbPerson)
{
return new Person
{
Id = dbPerson.Id,
GivenName = dbPerson.GivenName,
};
}
public static Person ToAnonymousPerson(this DbPerson dbPerson)
{
return new Person
{
Id = dbPerson.Id,
GivenName = dbPerson.AltGivenName,
};
}
}
人員控制器.cs
public class PersonsController : ODataController
{
private readonly PersonContext _context;
public PersonsController(PersonContext context)
{
_context = context;
}
[EnableQuery]
public IActionResult Get()
{
return new ObjectResult(_context.Persons.Select(MappingHelper.ToPerson));
}
}
運行以下查詢需要 5-10 秒 http://localhost:4871/odata/persons?$top=10
如果我改為更改:
return new ObjectResult(_context.Persons.Select(MappingHelper.ToPerson));
至
return new ObjectResult(_context.Persons);
和改變
var persons = builder.EntitySet<Person>("Persons");
至
var persons = builder.EntitySet<DbPerson>("Persons");
相同的查詢需要 50-100 毫秒
person 表中有大約 150k 人。
配置PersonsUnrestrictedController
以返回標准的數據庫操作集,這實際上是您的內部 DbPerson api ,並將PersonsController
定義為專用 controller 以提供對名為Person
的數據傳輸 Z497031794414A55524B5 的訪問。
您已經定義了大部分元素,我們需要更改的只是 controller 實現。
以下內容沒有變化:
在EdmModel
中定義兩個控制器:
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var persons = builder.EntitySet<Person>("Persons");
var unrestricted = builder.EntitySet<DbPerson>("PersonsUnrestricted");
return builder.GetEdmModel();
}
與其創建到 map 對象的映射方法,更簡單的模式是在 controller 中添加一個方法,以提供該 controller 中的所有操作都應使用的基本查詢。
通過這種方式,您可以強制執行通用過濾條件、包含或排序,而無需在每個操作中聲明相同的查詢。 它是一種更容易長期維護的模式,當您有 20 個操作或函數都具有需要重構的相同查詢時,或者當您必須跨多個控制器重構類似條件時,您會感謝我。
公眾人物Controller:
public class PersonsController : ODataController
{
private readonly PersonContext _context;
public PersonsController(PersonContext context)
{
_context = context;
}
private IQueryable<Person> GetQuery()
{
return from p in _context.Persons
select new Person
{
Id = p.Id,
GivenName = p.AltGivenName
};
}
[EnableQuery]
public IActionResult Get()
{
return Ok(GetQuery());
}
[EnableQuery]
public IActionResult Get(int key)
{
return Ok(GetQuery().Single(x => x.Id == key));
}
}
不受限制的 Controller:
public class PersonsUnrestrictedController : ODataController
{
private readonly PersonContext _context;
public PersonsUnrestrictedController(PersonContext context)
{
_context = context;
}
private IQueryable<DbPerson> GetQuery()
{
return _context.Persons;
}
[EnableQuery]
public IActionResult Get()
{
return Ok(GetQuery());
}
[EnableQuery]
public IActionResult Get(int key)
{
return Ok(GetQuery().Single(x => x.Id == key));
}
}
這里的其他答案特別關注您對映射到同一個表的 2 個單獨控制器的請求,但聽起來您真正需要的是來自 OData 實體的自定義只讀提要,它仍然具有查詢支持。
在 OData 中,這通常通過在返回一組可查詢 DTO 的標准 controller 上定義Function
端點來實現。
以下內容沒有變化:
然而,在這個解決方案中,我們將有一個PersonsController
,它具有標准的Get()
端點和一個Function View()
端點。
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var persons = builder.EntitySet<DbPerson>("Persons");
persons.EntityType.Collection.Function("View").ReturnsCollection<Person>();
return builder.GetEdmModel();
}
人員控制器.cs
public class PersonsController : ODataController
{
private readonly PersonContext _context;
public PersonsController(PersonContext context)
{
_context = context;
}
[HttpGet]
[EnableQuery]
public IActionResult Get()
{
return Ok(_context.Persons);
}
[HttpGet]
[EnableQuery]
public IActionResult View()
{
return Ok(from p in _context.Persons
select new Person
{
Id = p.Id,
GivenName = p.AltGivenName
});
}
}
OP 專門詢問了
asp.net-core
,但是此響應在EF6
和asp.net
和asp.net-core
中都有效
在這種情況下(事實上,作為一般規則),我建議您將數據庫模型與業務模型分開,並在兩者之間設置一些映射邏輯。
這樣,您就可以擁有兩個業務模型Person
和AnonymousPerson
以及一個數據庫 model DbPerson
(隨便稱呼它們)。 然后,您實現一些映射邏輯以將DbPerson
轉換為Person
並將DbPerson
轉換為AnonymousPerson
,並根據您所處的情況使用適當的映射邏輯。
例如,看看這個 fiddle 。 有關更多詳細信息,請考慮我們有以下數據庫 model 和兩個商業 object 模型:
public class DbPerson
{
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string AltGivenName { get; set; }
}
public class Person
{
public string GivenName { get; set; }
public string FamilyName { get; set; }
}
public class AnonymousPerson
{
public string Nickname { get; set; }
}
然后我們需要一些邏輯來將數據庫 model 轉換為兩個業務對象之一。 我在這里使用擴展方法,但如果您願意,也可以只使用普通方法:
public static class MappingHelper
{
public static Person ToPerson(this DbPerson dbPerson)
{
return new Person
{
GivenName = dbPerson.GivenName,
FamilyName = dbPerson.FamilyName
};
}
public static AnonymousPerson ToAnonymousPerson(this DbPerson dbPerson)
{
return new AnonymousPerson
{
Nickname = dbPerson.AltGivenName
};
}
}
現在,要以正確的格式獲取數據,您只需使用所需的映射方法(使用類似實體框架的方法):
// From PersonsController (or a service beneath)
var persons = myDatabase.Persons.Select(MappingHelper.ToPerson);
// Same as Select(p => p.ToPerson())
// or Select(p => MappingHelper.ToPerson(p))
// And from PersonsUnrestrictedController
var anonPersons = myDatabase.Persons.Select(MappingHelper.ToAnonymousPerson);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.