使用Angular和ASP.NET Web API以及EF Code First的SELECT问题

时间:2014-09-23 18:49:50

标签: asp.net angularjs select data-binding asp.net-web-api

我遇到了使用SELECT来处理Angular,ASP.NET Web API v2和Entity Framework Code First的问题。我有两个简单的对象,产品和类别,使用以下模型定义:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string SerialNumber { get; set; }
    public string Notes { get; set; }
    public Category Category { get; set; }
    public ApplicationUser User { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ApplicationUser User { get; set; }

}

然后我有以下Angular Markup:

<div class="form-group">
    <label for="inputSerial" class="col-lg-3 control-label">Category</label>
    <div class="col-lg-9">
        <select class="form-control" id="selectCategory" ng-model="product.Category" ng-options="category as category.Name for category in categories"></select>
    </div>
</div>

最后,以下Angular app.js脚本:

app.controller('SerialController', function ($scope, $http, $routeParams, $cookies) {

    $http({
        method: 'GET',
        url: 'http://localhost:20697/api/categories/' + '?access_token=' + $cookies.token,
        headers: { 'Authorization': 'Bearer ' + $cookies.token }
    }).success(function (data, status, headers) {
        $scope.categories = data;
    }).error(function (data, status, headers) {
        $scope.error = reason;
    });

    var onGetComplete = function (response) {
        $scope.product = response.data;
    };

    var onError = function (reason) {
        $scope.error = reason;
    };

    if ($routeParams.id > 0) {

        $http.get("http://localhost:20697/api/products/" + $routeParams.id)
            .then(onGetComplete, onError);
    }

    $scope.saveProduct = function () {

        if ($scope.product.Id > 0) {
            $http({
                method: 'PUT',
                data: $scope.product,
                url: 'http://localhost:20697/api/products/' + $scope.product.Id
            }).success(function (data, status, headers) {

            }).error(function (data, status, headers) {

            });
        }
        else {
            $http({
                method: 'POST',
                data: $scope.product,
                url: 'http://localhost:20697/api/products/'
            }).success(function (data, status, headers) {

            }).error(function (data, status, headers) {

            });
        }
    };
});

有了这个,我正在使用数据库中的类别正确填充SELECT。但是,我有两个问题。

问题#1)如果在数据库中设置了类别,则会将其从Web api发送到Angular,但实际上并未在select中选择正确的条目。

问题#2)当我在下拉列表中选择一个条目并保存时,angular正在将产品对象发送回具有正确类别集的api。我甚至可以在api控制器中看到它。但实际上并没有将类别选择保存到数据库中的产品中。这是我的产品更新控制器代码:

// PUT: api/Products/5
[ResponseType(typeof(void))]
public IHttpActionResult PutProduct(int id, Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != product.Id)
    {
        return BadRequest();
    }

    db.Entry(product).State = EntityState.Modified;

    try
    {
        db.SaveChanges();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

在实际数据库中,Entity Framework Code首先将products表中的类别添加为Category_Id,这是我所期望的。

对此有任何帮助将不胜感激。谢谢!

更新:在以下建议不能正常工作之后,我在网上进行了更多搜索。我从Scott Allen那里找到了这篇文章。 http://odetocode.com/blogs/scott/archive/2013/06/19/using-ngoptions-in-angularjs.aspx。从那以后,我能够使用我的原始代码,但随后在角度控制器中添加一些代码来进行初始选择。我还必须改变控制器中的事物顺序(一旦我开始工作,我需要进一步重构它)。这是控制器的更新部分:

$http({
    method: 'GET',
    url: 'http://localhost:20697/api/categories/' + '?access_token=' + $cookies.token,
    headers: { 'Authorization': 'Bearer ' + $cookies.token }
}).success(function (data, status, headers) {
    $scope.categories = data;
    if ($routeParams.id > 0) {
        $http.get("http://localhost:20697/api/products/" + $routeParams.id)
            .then(onGetComplete, onError);
    }
}).error(function (data, status, headers) {
    $scope.error = reason;
});

var onGetComplete = function (response) {
    $scope.product = response.data;

    for (var i = 0; i < $scope.categories.length; i++) {
        if ($scope.categories[i].Id == $scope.product.Category.Id) {
            $scope.product.Category = $scope.categories[i];
            break;
        }
    }
};

有了这个,我现在能够使初始选择工作,并且正确的类别选择将被发送回API。 问题是,无论出于何种原因,实体框架在更新产品时都会跳过类别选择。以下是正在执行的T-SQL:

DECLARE @2 AS SQL_VARIANT;
DECLARE @0 AS SQL_VARIANT;
DECLARE @1 AS SQL_VARIANT;
SET @2 = NULL;
SET @0 = NULL;
SET @1 = NULL;

UPDATE [dbo].[Products]
SET [Name] = @0, [SerialNumber] = @1, [Notes] = NULL
WHERE ([Id] = @2)

如您所见,Category_Id根本没有得到更新。有什么我必须要告诉EF它应该更新类别的外键以及产品信息吗?

由于

2 个答案:

答案 0 :(得分:0)

这是你的问题的角度部分的想法。我们在选择框中尝试使用对象时遇到了问题。在Angular中,ngModel按引用进行比较,而不是值。绑定到对象数组时这很重要。我猜你的项目没有被选中,因为你的参考文献是不同的。我们现在写这样的ng-option表达式:

<select ng-model="product.category.id" ng-option="category.id as category.Name for category in categories" />

而不是匹配整个对象,这将允许您只匹配id或我们认为有点清洁的其他属性。希望这有帮助!

更新

试试这个,看看它是否适合你:

查看更新:

<select ng-model="categoryId" ng-option="category.id as category.Name for category in categories" />

控制器更新:

app.controller('SerialController', function ($scope, $http, $routeParams, $cookies) {
$scope.categoryId = 0;

$http({
    method: 'GET',
    url: 'http://localhost:20697/api/categories/' + '?access_token=' + $cookies.token,
    headers: { 'Authorization': 'Bearer ' + $cookies.token }
}).success(function (data, status, headers) {
    $scope.categories = data;
}).error(function (data, status, headers) {
    $scope.error = reason;
});

var onGetComplete = function (response) {
    $scope.product = response.data;
    $scope.categoryId = $scope.product.category.Id;
};

var onError = function (reason) {
    $scope.error = reason;
};

if ($routeParams.id > 0) {

    $http.get("http://localhost:20697/api/products/" + $routeParams.id)
        .then(onGetComplete, onError);
}

$scope.saveProduct = function () {
    $scope.product.category.Id = $scope.categoryId;

    if ($scope.product.Id > 0) {
        $http({
            method: 'PUT',
            data: $scope.product,
            url: 'http://localhost:20697/api/products/' + $scope.product.Id
        }).success(function (data, status, headers) {

        }).error(function (data, status, headers) {

        });
    }
    else {
        $http({
            method: 'POST',
            data: $scope.product,
            url: 'http://localhost:20697/api/products/'
        }).success(function (data, status, headers) {

        }).error(function (data, status, headers) {

        });
    }
};

});

答案 1 :(得分:0)

所以在继续挖掘并尝试之后。看起来这是由于我只有一个导航属性而没有外键返回到类别表。我真的不喜欢这个解决方案,但它确实有效。我认为有两件事情很糟糕,如果有人能给我一些关于更好修复方法的想法,我会很高兴。

第一步修复:我必须更新我的Product类以包含对类别的外键引用:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string SerialNumber { get; set; }
    public string Notes { get; set; }

    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
    public ApplicationUser User { get; set; }
}

然后在让Entity Framework更新我的数据库之后遇到各种痛苦的挣扎之后,我创建了一个全新的数据库并且它有效。而不是数据库现在在products表中具有列Category_Id,它现在具有预期的CategoryId。我第一次想到的就是这个。我真的不希望没有这个额外的财产来处理。

第二步修复:这是添加其他类别的副产品。我相信我可以通过更改我的ng-model和ng-options来使用CategoryId而不是对象来修复它。但是为了时间的推移,我继续在更新中将以下代码添加到ProductsController:

if (product.Category.Id != product.CategoryId)
{
    product.CategoryId = product.Category.Id;
}

理想情况下..如果我可以按原样离开我的模型,并且只有导航属性,那将是最好的。也许我会通过注释添加外键。如果有人想知道如何改善这一点,请告诉我。

谢谢!