在ASP.NET Web API中使用OData的单例模式

从OData v4开始增加了对单例模式的支持,我们不用每次根据主键等来获取某个EDM,就像在C#中使用单例模式一样。实现方式大致需要两步:

1、在需要实现单例模式的导航属性上加上[Singleton]特性
2、在EDM配置的时候使用builder.Singleton<SomeModel>("SomeModels")来创建SingletonConfiguration<SomeModel>

首先还是从模型开始。

public class Employee
{
    public int ID { get; set; }
    public string Name { get; set; }

    [Singleton]
    public Company Company { get; set; }
}

public enum CompanyCategory
{
    IT = 0,
    Communication = 1,
    Electronics = 2,
    Others = 3
}

public class Company
{
    public int ID { get; set; }
    public string Name { get; set; }
    public Int64 Revenue { get; set; }
    public CompanyCategory Category { get; set; }
    public List<Employee> Employees { get; set; }
}

以上,Company和Employee存在1对多关系,我们在Employee的Compnay导航属性上加上了[Singleton]特性,也就意味着我们希望在Company上使用单例模式。

然后就在WebApiConfig中配置如下:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...

        config.MapODataServiceRoute("ODataRoute", "odata", GetEdmModel());
    }

    public static IEdmModel GetEdmModel()
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

        EntitySetConfiguration<Employee> employeesConfiguration = builder.EntitySet<Employee>("Employees");
        EntityTypeConfiguration<Employee> employeeTypeConfiguration = employeesConfiguration.EntityType;
        employeeTypeConfiguration.Action("ResetDataSource");


        SingletonConfiguration<Company> companiesConfiguration = builder.Singleton<Company>("Umbrella");
        companiesConfiguration.EntityType.Action("ResetDataSource");
        companiesConfiguration.EntityType.Function("GetEmployeesCount").Returns<int>();

        builder.Namespace = "Hello";

        return builder.GetEdmModel();
    }
}

以上,builder.Singleton<Company>("Umbrella")方法创建SingletonConfiguration<Company>类型的实例,这是EDM实现单例的方式。


Company对应的控制器UmbrellaController

再来看Company对应的控制器,大致如下:

public class UmbrellaController : ODataController
{
    public static Company Umbrella;

    static UmbrellaController()
    {
        InitData();
    }

    private static void InitData()
    {
        Umbrella = new Company()
        {
            ID = 1,
            Name = "Umbrella",
            Revenue = 1000,
            Category = CompanyCategory.Communication,
            Employees = new List<Employee>()
        };
    }
    
    ...
    
    [HttpPost]
    public IHttpActionResult ResetDataSourceOnCompany()
    {
        InitData();
        return StatusCode(HttpStatusCode.NoContent);
    }

    public IHttpActionResult GetEmployeesCount()
    {
        return Ok(Umbrella.Employees.Count);
    }
}

以上,UmbrellaController提供的静态Company类型的Umbrella可以在全局获取。ResetDataSourceOnCompany对应配置单例EDM的companiesConfiguration.EntityType.Action("ResetDataSource")的Action,GetEmployeesCount对应配置单例EDM的companiesConfiguration.EntityType.Function("GetEmployeesCount").Returns<int>()的Function。

● 查询

[EnableQuery]
public IHttpActionResult Get()
{
    return Ok(Umbrella);
}

public IHttpActionResult GetRevenueFromCompany()
{
    return Ok(Umbrella.Revenue);
}

public IHttpActionResult GetName()
{
    return Ok(Umbrella.Employees);
}

以上,GetRevenueFromCompany和GetName分别获取属性,要符合惯例,即"Get+属性名称"。

● 添加

public IHttpActionResult Put(Company newCompany)
{
    Umbrella = newCompany;
    return StatusCode(HttpStatusCode.NoContent);
}

● Patch

public IHttpActionResult Patch(Delta<Company> item)
{
    item.Patch(Umbrella);
    return StatusCode(HttpStatusCode.NoContent);
}

● 创建Company上的Employees关系

/// <summary>
/// 创建Company上Employees的关系
/// </summary>
/// <param name="navigationProperty"></param>
/// <param name="link">Empolyee的uri地址</param>
/// <returns></returns>
[AcceptVerbs("POST")]
public IHttpActionResult CreateRef(string navigationProperty, [FromBody] Uri link)
{
    //获取Employee的外键
    int employeeId = HelperFunction.GetKeyValue<int>(link);
    Employee employee = EmployeesController.Employees.First(x => x.ID == employeeId);

    if(employee == null || navigationProperty!="Employees")
    {
        return BadRequest();
    }

    if(Umbrella.Employees == null)
    {
        Umbrella.Employees = new List<Employee>() { employee};
    }
    else
    {
        Umbrella.Employees.Add(employee);
    }
    return StatusCode(HttpStatusCode.NoContent);
}

其实就是往Company的Employees集合导航属性中添加一个元素。其中,HelperFunction.GetKeyValue<int>()方法用来获取link中Empoyee的主键。如下:

public static class HelperFunction
{
    //获取主键值
    public static TKey GetKeyValue<TKey>(Uri uri)
    {
        if(uri ==null)
        {
            throw new ArgumentException("uri");
        }

        var rootPath = uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.LastIndexOf('/') + 1);
        var odataUriParser = new ODataUriParser(WebApiConfig.GetEdmModel(), new Uri(rootPath), uri);
        var odataPath = odataUriParser.ParsePath();
        var keySegment = odataPath.LastSegment as KeySegment;
        if(keySegment==null)
        {
            throw new InvalidOperationException("The link does not contain a key");
        }
        return (TKey)keySegment.Keys.First().Value;
    }
}

● 删除Company上的Employees关系

/// <summary>
/// 删除关系
/// </summary>
/// <param name="relatedKey">Employee的主键</param>
/// <param name="navigationProperty"></param>
/// <returns></returns>
public IHttpActionResult DeleteRef(string relatedKey, string navigationProperty)
{
    int key = int.Parse(relatedKey);
    Employee employee = Umbrella.Employees.First(x => x.ID == key);

    if(navigationProperty != "Employees")
    {
        return BadRequest();
    }

    Umbrella.Employees.Remove(employee);
    return StatusCode(HttpStatusCode.NoContent);
}

其实就是删除Company的集合属性Employees中的一个Employee元素。


● 往Company的Employees集合里添加一个Employee元素

/// <summary>
/// 从Compnay处添加某个Employee
/// </summary>
/// <param name="employee"></param>
/// <returns></returns>
[HttpPost]
public IHttpActionResult PostToEmployees([FromBody] Employee employee)
{
    EmployeesController.Employees.Add(employee);
    if(Umbrella.Employees == null)
    {
        Umbrella.Employees = new List<Employee>() { employee };
    }
    else
    {
        Umbrella.Employees.Add(employee);
    }
    return Created(employee);
}

EmployeesController不详诉

public class EmployeesController : ODataController
{
    public static List<Employee> Employees;

    static EmployeesController()
    {
        InitData();
    }

    private static void InitData()
    {
        Employees = Enumerable.Range(0, 10).Select(i =>
               new Employee()
               {
                   ID = i,
                   Name = string.Format("Name {0}", i)
               }).ToList();
    }

    [EnableQuery]
    public IHttpActionResult Get()
    {
        return Ok(Employees.AsQueryable());
    }

    [EnableQuery]
    public IHttpActionResult Get(int key)
    {
        return Ok(Employees.Where(e => e.ID == key));
    }

    public IHttpActionResult GetCompanyFromEmployee([FromODataUri] int key)
    {
        var company = Employees.First(e => e.ID == key).Company;
        if(company==null)
        {
            return StatusCode(HttpStatusCode.NotFound);
        }
        return Ok(company);
    }

    public IHttpActionResult Post([FromBody] Employee employee)
    {
        Employees.Add(employee);
        return Created(employee);
    }

    [AcceptVerbs("PUT")]
    public IHttpActionResult CreateRef([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
    {
        if(navigationProperty!="Company")
        {
            return BadRequest();
        }
        Employees.First(e => e.ID == key).Company = UmbrellaController.Umbrella;
        return StatusCode(HttpStatusCode.NoContent);

    }


    public IHttpActionResult DeleteRef([FromODataUri] int key, string navigationProperty)
    {
        if(navigationProperty!="Company")
        {
            return BadRequest();
        }

        Employees.First(e => e.ID == key).Company = null;
        return StatusCode(HttpStatusCode.NoContent);
    }

    public IHttpActionResult PutToCompany(int key, Company company)
    {
        var navigateCompany = Employees.First(e => e.ID == key).Company;
        Employees.First(e => e.ID == key).Company = company;
        if(navigateCompany.Name == "Umbrella")
        {
            //体现Singleton
            UmbrellaController.Umbrella = navigateCompany;
        }
        else
        {
            return BadRequest();
        }
        return StatusCode(HttpStatusCode.NoContent);
    }

    public IHttpActionResult PatchToCompany(int key, Delta<Company> company)
    {
        var navigateCompan = Employees.First(e => e.ID == key).Company;
        company.Patch(Employees.First(e => e.ID == key).Company);

        if(navigateCompan.Name == "Umbrella")
        {
            company.Patch(UmbrellaController.Umbrella);
        }
        else
        {
            return BadRequest();
        }
        return StatusCode(HttpStatusCode.NoContent);
    }

    [HttpPost]
    public IHttpActionResult ResetDataSourceOnCollectionOfEmployee()
    {
        InitData();
        return Ok();
    }
}
原文地址:https://www.cnblogs.com/darrenji/p/4952184.html