簡體   English   中英

實體框架 + ODATA + 動態列映射

[英]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 實現。

以下內容沒有變化:

  • 數據庫人.cs
  • 個人.cs
  • PersonContext.cs

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端點來實現。

以下內容沒有變化:

  • 數據庫人.cs
  • 個人.cs
  • PersonContext.cs

然而,在這個解決方案中,我們將有一個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 ,但是此響應在EF6asp.netasp.net-core中都有效

在這種情況下(事實上,作為一般規則),我建議您將數據庫模型與業務模型分開,並在兩者之間設置一些映射邏輯。

這樣,您就可以擁有兩個業務模型PersonAnonymousPerson以及一個數據庫 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM