0

Я пытаюсь обновить многие до многих отношений, которые у меня установлены в Entity Framework, используя Code First. Я создал следующие Модели.Невозможно обновить многие до многих отношений. Entity Framework Code First

[Serializable] 
public class ClientFormField : FormField 
{ 
    public ClientFormField() 
    { 
     CanDisable = true; 
    } 

    public virtual ClientFormGroup Group { get; set; } 
    public virtual ICollection<ClientValue> Values { get; set; } 

    public virtual ICollection<LetterTemplate> LetterTemplates { get; set; } 
} 

[Serializable] 
public class CaseFormField : FormField 
{ 
    public CaseFormField() 
    { 
     CanDisable = true; 
    } 

    public virtual CaseFormGroup Group { get; set; } 

    public virtual ICollection<LetterTemplate> LetterTemplates { get; set; } 

    public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     foreach (var val in base.Validate(validationContext)) 
      yield return val; 
    } 
} 

[Serializable] 
public class SystemField : TrackableEntity 
{ 
    public string Name { get; set; } 
    public string Value { get; set; } 
    public string VarName { get; set; } 
    public virtual SystemFieldType SystemFieldType { get; set; } 
    public int TypeId { get; set; } 

    public ICollection<LetterTemplate> LetterTemplates { get; set; } 

    public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     if (string.IsNullOrWhiteSpace(Name)) 
      yield return new ValidationResult("System field must have a name.", new[] { "SystemFieldName" }); 

     if (string.IsNullOrWhiteSpace(Value)) 
      yield return new ValidationResult("System field must have a value.", new[] { "SystemFieldValue" }); 

     var regex = new Regex(@"^[a-zA-Z0-9-_]+$"); 
     if (!string.IsNullOrWhiteSpace(VarName) && !regex.IsMatch(VarName)) 
      yield return 
       new ValidationResult("Varname can only contain alphanumeric, underscore, or hyphen", 
            new[] { "SystemFieldVarName" }); 

     if (TypeId <= 0) 
      yield return new ValidationResult("System Field must have a type.", new[] { "SystemFieldType" }); 
    } 
} 

[Serializable] 
public class LetterTemplate : TrackableEntity 
{ 
    public LetterTemplate() 
    { 
     ClientFields = new Collection<ClientFormField>(); 
     CaseFields = new Collection<CaseFormField>(); 
     SystemFields = new Collection<SystemField>(); 
    } 

    public string Name { get; set; } 
    public string Data { get; set; } 

    public virtual ICollection<ClientFormField> ClientFields { get; set; } 
    public virtual ICollection<CaseFormField> CaseFields { get; set; } 
    public virtual ICollection<SystemField> SystemFields { get; set; } 

    public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     if(string.IsNullOrWhiteSpace(Name)) 
      yield return new ValidationResult("Form Template must have a name", new[] { "Name" }); 

     if(string.IsNullOrWhiteSpace(Data)) 
      yield return new ValidationResult("Form Template must have content", new[] { "Data" }); 
    } 
} 

Ниже приведена конфигурация класса LetterTemplate.

public class LetterTemplateConfiguration : BaseTrackableEntityConfiguration<LetterTemplate> 
{ 
    public LetterTemplateConfiguration() 
    { 
     HasMany(c => c.ClientFields).WithMany(c => c.LetterTemplates) 
      .Map(m => 
        { 
         m.MapLeftKey("LetterTemplateId"); 
         m.MapRightKey("ClientFormFieldId"); 
         m.ToTable("LetterTemplateClientFields"); 
        }); 

     HasMany(c => c.CaseFields).WithMany(c => c.LetterTemplates) 
      .Map(m => 
        { 
         m.MapLeftKey("LetterTemplateId"); 
         m.MapRightKey("CaseFormFieldId"); 
         m.ToTable("LetterTemplateCaseFields"); 
        }); 

     HasMany(c => c.SystemFields).WithMany(c => c.LetterTemplates) 
      .Map(m => 
        { 
         m.MapLeftKey("LetterTemplateId"); 
         m.MapRightKey("SystemFieldId"); 
         m.ToTable("LetterTemplateSystemFields"); 
        }); 
    } 
} 

Вот метод управления для Добавить/обновить и методы сервера, который содержит бизнес-логику Добавить/обновить

[HttpPost] 
[ValidateAntiForgeryToken] 
[ValidateInput(false)] 
public ActionResult Manage(LetterTemplate template) 
{ 
    if(ModelState.IsValid) 
    { 
     if (_letterTemplateService.Save(template) != null) 
      return RedirectToAction("List"); 
    } 

    ViewBag.ClientFields = _clientFieldService.GetAllFields().OrderBy(f => f.Name); 
    ViewBag.CaseFields = _caseFieldService.GetAllFields().OrderBy(f => f.Name); 
    ViewBag.SystemFields = _systemFieldService.GetAllFields().OrderBy(f => f.Name); 

    return View(template); 
} 

public LetterTemplate Save(LetterTemplate template) 
{ 
    var dbTemplate = template; 

    if (template.Id > 0) 
    { 
     dbTemplate = _letterTemplateRepo.GetById(template.Id); 
     dbTemplate.Name = template.Name; 
     dbTemplate.Data = template.Data; 
    } 

    dbTemplate.ClientFields.Clear(); 
    foreach (var field in _clientFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~"))) 
     dbTemplate.ClientFields.Add(field); 

    dbTemplate.CaseFields.Clear(); 
    foreach (var field in _caseFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~"))) 
     dbTemplate.CaseFields.Add(field); 

    dbTemplate.SystemFields.Clear(); 
    foreach (var field in _systemFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~"))) 
     dbTemplate.SystemFields.Add(field); 

    return template.Id <= 0 ? _letterTemplateRepo.Add(dbTemplate) : _letterTemplateRepo.Update(dbTemplate); 
} 

Вот вид на Add/Update в шаблоне письма.

@section RightContent 
{ 
<h4>Manage</h4> 
<form method="POST" action="/LetterTemplate/Manage" id="templateForm"> 
    <div id="toolbar"> 
     <div style="float: left;padding: 3px 0px 0px 10px;"> 
      @Html.LabelFor(m => m.Name) 
      @Html.TextBoxFor(m => m.Name) 
     </div> 
     <div class="item" onclick=" $('#templateForm').submit(); "> 
      <span class="save"></span>Save 
     </div> 
     <div class="item" onclick=" window.location = '/LetterTemplate/List'; "> 
      <span class="list"></span>Back To List 
     </div> 
    </div> 
    <div class="formErrors"> 
     @Html.ValidationSummary() 
    </div> 
    @Html.HiddenFor(m => m.Id) 
    @Html.HiddenFor(m => m.Active) 
    @Html.HiddenFor(m => m.IsDeleted) 
    @Html.TextAreaFor(m => m.Data) 
    @Html.AntiForgeryToken() 
</form> 
} 

Когда я создаю новый шаблон из представления, все работает нормально. Я получаю поля, заполненные в моих таблицах отношений «Множество-много», как я ожидаю. Когда я пытаюсь обновить отношения, которые должны очищать все существующие отношения и создавать новые отношения, ничего не происходит. Таблицы не затронуты вообще. Я прочитал несколько разных сообщений о проблемах с обновлением многих-многих таблиц, но ничего не нашел, что устраняет мою проблему. Это первый раз, когда я попытался сделать много ко многим с кодом EF и после многих упражнений прошел несколько руководств, но кажется, что независимо от того, что я делаю, EF не будет обновлять таблицы отношений.

UPDATE:

Запросы, полученные при добавлении нового шаблона:

DECLARE @0 nvarchar = N'Test', 
     @1 nvarchar = N'<p>~~case_desc~~</p> 

<p>~~fname~~</p> 

<p>~~lname~~</p> 
', 
     @2 bit = 1, 
     @3 bit = 0, 
     @4 int = 2, 
     @5 int = 2, 
     @6 DateTime2 = '2013-04-08T16:36:09', 
     @7 DateTime2 = '2013-04-08T16:36:09' 

insert [dbo].[LetterTemplates]([Name], [Data], [Active], [IsDeleted], [CreatedById], [ModifiedById], [DateCreated], [DateModified]) 
values (@0, @1, @2, @3, @4, @5, @6, @7) 


DECLARE @0 int = 2, 
     @1 int = 1 

insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId]) 
values (@0, @1) 

DECLARE @0 int = 2, 
     @1 int = 2 

insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId]) 
values (@0, @1) 

DECLARE @0 int = 2, 
     @1 int = 3 

insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId]) 
values (@0, @1) 

Запрос сгенерированных на обновление:

DECLARE @0 nvarchar = N'Test', 
     @1 nvarchar = N'<p>~~case_desc~~</p> 

<p> </p> 

<p>~~fname~~</p> 

<p> </p> 

<p>~~dob~~</p> 
', 
     @2 bit = 1, 
     @3 bit = 0, 
     @4 int = 2, 
     @5 int = 2, 
     @6 DateTime2 = '2013-04-08T16:23:12', 
     @7 DateTime2 = '2013-04-08T16:33:15', 
     @8 int = 1 

update [dbo].[LetterTemplates] 
set [Name] = @0, [Data] = @1, [Active] = @2, [IsDeleted] = @3, [CreatedById] = @4, [ModifiedById] = @5, [DateCreated] = @6, [DateModified] = @7 
where ([Id] = @8) 

UPDATE

У моего шаблона репозитория есть 2 базовых класса. Базовый трекинг-репозиторий и базовый репозиторий. Базовое отслеживание реквизитов объекта отслеживает удаление удаленных элементов, удаление ненужных элементов и управление созданным/измененным и созданным датом/обновлением. Базовое репо обрабатывает остальные основные операции CRUD. Ниже приведен метод обновления и связанные методы, которые вызываются при вызове обновления через LetterTemplateRepository. Поскольку это репо наследует базу отслеживаемого репо-объекта, оно запускает обновление из базового класса.

public override T Update(T entity) 
{ 
    return Update(entity, false); 
} 

public override T Update(T entity, bool attachOnly) 
{ 
    InsertTeData(ref entity); 
    entity.ModifiedById = CurrentUserId; 
    entity.DateModified = DateTime.Now; 
    _teDB.Attach(entity); 
    _db.SetModified(entity); 
    if (!attachOnly) _db.Commit(); 
    return entity; 
} 

private void InsertTeData(ref T entity) 
{ 
    if (entity == null || entity == null) return; 
    var dbEntity = GetById(entity.Id); 
    if (dbEntity == null) return; 
    _db.Detach(dbEntity); 
    entity.CreatedById = dbEntity.CreatedById; 
    entity.DateCreated = dbEntity.DateCreated; 
    entity.ModifiedById = dbEntity.ModifiedById; 
    entity.DateModified = dbEntity.DateModified; 
} 

Метод SetModified в DbContext просто устанавливает модификацию EntityState. Я использую Fake DbContext и DbSet в своих модульных тестах, поэтому любые специальные вызовы EF я расширяю через DbContext, чтобы мои тесты могли работать без необходимости создания группы Fake Repositories.

+0

Что делает ваш '_letterTemplateRepo.Update()' метод? –

+0

Я обновил свой вопрос с помощью вызовов в своих классах репозитория для метода обновления. – DSlagle

+1

Не могли бы вы попробовать эксперимент? Замените '_letterTemplateRepo.Update (dbTemplate)' на '_letterTemplateRepo.Save()', который просто вызывается '_db.SaveChanges()'. –

ответ

0

Оказывается, проблема была в методе InsertTeData. Когда он отделял сущность, которую я вытащил из db, чтобы убедиться, что создан и создал дату, она заставила сущность, с которой я работал, потерять всю информацию о многих отношениях. Мое предположение - это способ отслеживания сущностей, и оба они имеют один и тот же ключ.

Я удалил метод InsertTeData и теперь управляю всем как значения по умолчанию в конструкторе абстрактного класса TrackableEntity, и все работает сейчас.

public override T Update(T entity, bool attachOnly) 
{ 
    entity.ModifiedById = CurrentUserId; 
    entity.DateModified = DateTime.Now; 
    _teDB.Attach(entity); 
    _db.SetModified(entity); 
    if (!attachOnly) _db.Commit(); 
    return entity; 
} 

После выполнения всех моих модульных тестов, тестов интеграции и некоторых ручных тестов все прошло так, что я в порядке с этим изменением.