【问题标题】:Issue with SELECT using Angular and ASP.NET Web API and EF Code First使用 Angular 和 ASP.NET Web API 和 EF Code First 的 SELECT 问题
【发布时间】:2014-09-23 18:49:50
【问题描述】:

我在让 SELECT 与 Angular、ASP.NET Web API v2 和 Entity Framework Code First 一起工作时遇到问题。我有两个简单的对象,Products 和 Categories,用以下模型定义:

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 标记:

<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。 问题是,无论出于何种原因,Entity Framework 在更新产品时都会跳过类别选择。这是正在执行的 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 它应该更新类别的外键以及产品信息吗?

谢谢

【问题讨论】:

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


    【解决方案1】:

    这是您问题的角度部分的想法。我们在尝试使用选择框中的对象时遇到了问题。在 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) {
    
            });
        }
    };
    

    });

    【讨论】:

    • 所以这确实解决了第一个问题,但使第二部分变得更糟。它现在确实正确绑定并选择了正确的条目,但现在它不再在保存时将正确的选择发送回 api。
    • 选择后的产品对象是什么样子的?哪些特定属性未更新?
    • 不确定这是否有帮助,但您可能需要修复您正在执行的检查以查看保存时产品 id 是否大于 0,因此 id 属性是小写的。 $scope.product.id
    • 例如,如果产品最初设置为类别 id = 5“MSDN”,则建议进行更改。 MSDN 在加载页面时正确显示。但是,我将其切换为id = 6, "Media/Graphics",然后点击保存后,发送的产品在返回API 的路上仍然设置了原来的id5 "MSDN"。
    • 查看我对答案所做的更新。看看有没有帮助。
    【解决方案2】:

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

    修复的第一步:我必须更新我的 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;
    }
    

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

    谢谢!

    【讨论】:

    • 我试图从 Product 类中删除外键,而是使用 fluent API 在 OnModelBuilding() 方法中指定它。然而,即使看起来 EF 喜欢 FK,它仍然忽略了更新。听起来上面的解决方案是必须要做的。
    猜你喜欢
    • 2011-09-29
    • 1970-01-01
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    • 2016-06-10
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    相关资源
    最近更新 更多