1

问题:

用户创建一个新帐户。必填字段之一是BusinessGroupBusinessGroup是导航参考属性。用户BusinessGroup从下拉框中选择,代码BusinessGroup在数据库中搜索,检索它并将其分配给帐户。听起来很简单,对吧?

出于某种原因,每次您保存一个新帐户时,它也会BusinessGroup在 BusinessGroups 的数据库表中插入另一个帐户,即使它已经存在,因为我从数据库中检索它并直接将其分配给帐户。EF上下文仍然认为它是一个新的。

顺便说一句,我使用代码优先,因为我遵循 TDD 方法。

这是我的 POCO:

    [Table("Account")]
    public class Account
    {
        [HiddenInput]
        public int Id { get; set; }

        [Required(ErrorMessage = "The Name is required")]
        public string Name { get; set; }

        public Guid? BusinessGroupId { get; set; }
        [Required(ErrorMessage = "The Business Group is required")]            
        public BusinessGroup BusinessGroup { get; set; }
    }

因为我想覆盖在数据库中生成的外键的命名约定,所以我还在上面指定了外键 BusinessGroupId。

这是商业集团 POCO:

        [Table("BsuinessGroup")]
        public class BusinessGroup
        {
            [HiddenInput]
            [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public Guid Id { get; set; }

            [Required]                
            public string Name { get; set; }
        }

这是根据用户从下拉框中选择的内容从数据库中获取 BusinessGroup 的代码,Id 是 GUID(请注意,此代码位于 MVC 自定义模型绑定器中,因此它公开了 ModelBinderContext:

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //see if there is an existing model to update and create one if not
            var account = (Account) bindingContext.Model ?? new Account();
            //find out if the value provider has a required prefix
            bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
            var prefix = hasPrefix ? String.Format("{0}.", bindingContext.ModelName) : String.Empty;
            _context = bindingContext;
            _prefix = prefix;

            //map the fields of the model object
            account.Id = Convert.ToInt32((GetValue("Id")));
            account.Name = (GetValue("Name"));
            account.Phone = (GetValue("Phone"));
            account.Fax = (GetValue("Fax"));
            account.Email = (GetValue("Email"));
            account.Website = (GetValue("Website"));

            account.Audit = new Audit{Created = DateTime.Now, CreatedBy = "renso"};

            //I changed this to assign the ID rather than assign the BusinessGroup itself since that did not work 
            //and causes a new BusinessGroup to be inserted into the DB on every save of an account,
                           // this solution seems to work but I am not sure if this is the correct behavior.
            **var tempBusinessGroup = ((Controllers.AccountController)controllerContext.Controller).
                BusinessGroupRepository.Find(GetGuidValue("BusinessGroup"));
            account.BusinessGroupId = tempBusinessGroup.Id;**

            return account;
        }
        private Guid GetGuidValue(string key)
        {
            var vpr = _context.ValueProvider.GetValue(_prefix + key);
            return vpr == null ? new Guid() : new Guid(vpr.AttemptedValue);
        }
        private string GetValue(string key)
        {
            var vpr = _context.ValueProvider.GetValue(_prefix + key);
            return vpr == null ? null : vpr.AttemptedValue;
        }
.............
    }

上面的粗体代码我为 BusinessGroupId 分配了一个值(GUID),而不是BusinessGroup似乎可以正常工作。这是 EF 的预期行为、错误还是什么?

如果我将其更改为下面的代码,则会导致BusinessGroup在我保存新帐户时创建新帐户的问题:

   account.BusinessGroup = ((Controllers.AccountController)controllerContext.Controller).
        BusinessGroupRepository.Find(GetGuidValue("BusinessGroup"));

这是我的行动:

[HttpPost]
public ActionResult Create(Account account)
{
    if (ModelState.IsValid)
    {
        AccountRepository.InsertOrUpdate(account);
        AccountRepository.Save();
        return RedirectToAction("List");
    }
    var viewData = new AccountControllerViewData { Account = account, BusinessGroups = BusinessGroupRepository.All };
    return View(viewData);
}

保存也失败了,由于某种原因,我需要在 AccountRepository 上的 Save 中关闭 ValidateOnSaveEnabled=false,不知道为什么它可以工作,它抱怨在BusinessGroup我设置帐户的 BusinessGroupId 属性时丢失:

public void InsertOrUpdate(Account account)
{
    if (account.Id == default(Int32))
    {
        // New entity
        _context.Account.Add(account);
    }
    else
    {
        // Existing entity
        _context.Entry(account).State = EntityState.Modified;
    }
}

public void Save()
{
    //need the validation switched off or it fails with an error
    _context.Configuration.ValidateOnSaveEnabled = false;
    _context.SaveChanges();
}

感觉就像我在这里遗漏了一些东西。我在 EF 2ed、Code-First 或 DbContext 书籍中找不到答案。似乎当您在 POCO 中定义自己的外键时,每当您想在我的示例中将导航引用从 Account 分配给 BusinessGroup 时,您不能直接分配给 BusinessGroup,您必须将值/键分配给外键,在本例中为 BusinessGroupId。难道我做错了什么?

4

2 回答 2

0

实际上,重构了代码,以便它使用相同的 DbContext 来检索帐户和业务组,这可以按预期工作,谢谢!现在的问题是,对于我的存储库模式,我从一个抽象类继承,该类为我提供了一个新的上下文,但这仍然不能解决我遇到的每个存储库都有自己的上下文的问题,你们是如何解决这个问题的? 例如,您创建一个新帐户,它具有业务组、国家/地区、州(如 PA、NJ)等的导航参考,每个都有自己的存储库,例如用于维护数据库中的国家/地区列表。一个帐户有一个国家导航参考,应该遵循什么模式来共享相同的上下文,我认为每个 http 请求一个?

于 2012-07-23T21:02:24.560 回答
0

BusinessGroupRepository和您很可能AccountRepository有自己的上下文实例。当您BusinessGroup从其中加载时,BusinessGroupRepository它发生在另一个上下文中,而不是AccountRepository您保存新的上下文的上下文中account。对于第二个上下文BusinessGroup是一个未知实体,EF 会将其保存为一个新实体。

重构您的代码以确保两个存储库将使用相同的上下文实例。上下文不应在存储库中创建,而应在外部创建并注入两者。或者将两个存储库合并到一个存储库中。

于 2012-07-19T19:11:47.530 回答