Каков наилучший / наиболее эффективный способ обновления подмодели / свойства в родительской модели / свойстве? - PullRequest
0 голосов
/ 30 мая 2018

Скажем, у меня есть ClassA, у которого есть свойства:

//ClassA
int ID
string Name
ClassB SubModel

И у меня также есть ClassB, у которого есть свойства:

//ClassB
int ID
string Name

Теперь у меня есть вид, гдепользователь может изменить ClassA, а также ClassA связанное свойство ClassB.

Каков наилучший способ изменения ClassB?

Прямо сейчас, когда яretrieve ClassA, я делаю .Include, чтобы получить его ClassB, и когда я предоставляю свойства пользователю, я делаю ClassA.SubModel.Name и т. д.

Это правильный способ сделать это?Или я должен взять ClassA и ClassB отдельно и иметь их как два отдельных свойства, и когда я обновляюсь, вызову два обновления, чтобы обновить их по отдельности?

Ответы [ 2 ]

0 голосов
/ 30 мая 2018

Я не думаю, что есть единственный «лучший» способ сделать это.Все зависит от вашего бизнеса.Поэтому я просто делюсь тем, как бы я это сделал.

Мой подход DDD

В моем практическом проекте по разработке Domain-Drive у меня есть 2 набора моделей, не включая модели представлений для представлений:

  • Доменные модели - представляют вашу бизнес-логику, не зависят от кода вашей инфраструктуры
  • Постоянные модели - представляют, как вы отображаете модели доменов в выбранной вами базе данных

Модели постоянства

Я могу определить ClassA и ClassB как сущности с отношением один-ко-многим.

Допустим, я использую Entity Framework Core в качестве ORM и SQL Serverв качестве базы данных.

public class ClassAEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int ClassBId { get; set; }
    public ClassBEntity ClassB { get; set; }
}

public class ClassBEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<ClassAEntity> ClassAs { get; set; }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options): base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Configure relationships
        builder.Entity<ClassAEntity>(b =>
        {
            b.HasKey(x => x.Id);
            b.Property(x => x.Name).IsRequired();

            b.HasOne(x => x.ClassB)
                .WithMany(y => y.ClassAs)
                .HasForeignKey(x => x.ClassBId);

            b.ToTable("ClassA");
        });

        builder.Entity<ClassBEntity>(b =>
        {
            b.HasKey(x => x.Id);
            b.Property(x => x.Name).IsRequired();

            b.ToTable("ClassB");
        });
    }

    public DbSet<ClassAEntity> ClassAs { get; set; }
    public DbSet<ClassBEntity> ClassBs { get; set; }
}

Доменные модели

Вы можете создавать свои доменные модели, полностью отличающиеся от ваших постоянных моделей.На самом деле в моем проекте ClassA и ClassB находятся в двух отдельных пространствах имен, и они оба Aggregate Root - им не разрешено ссылаться друг на друга напрямую.Они могут ссылаться друг на друга только по своей идентичности.Также вы видите эти частные сеттеры?

public class ClassA : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    public int ClassBId { get; private set; }

    private ClassA() { }

    private ClassA(CreateClassACommand command) : base(command.Id)
    {
        this.Name = command.Name;
        this.ClassBId = command.ClassBId;

        // You can create events and store them later on as auditing or
        // have others subscribe this event. 
        AddEvent(new ClassACreated
        {
            // ...
        });
    }

    public static ClassA CreateNew(CreateClassACommand command,
        IValidator<CreateClassACommand> validator)
    {
        // You can have validations here too, with help of FluentValidation
        // library
        validator.ValidateAndThrow(command);

        return new ClassA(command);
    }

    public void UpdateDetails(UpdateClassADetailsCommand command,
        IValidator<UpdateClassADetailsCommand> validator)
    {
        validator.ValidateAndThrow(command);

        this.Name = command.Name;
        this.ClassBId = command.SelectedClassBId;

        AddEvent(new ClassADetailsUpdated 
        {
            // ...
        });
    }
}

Надеемся, вы видите преимущества наличия класса для представления вашей бизнес-логики.У вас могут быть частные сеттеры, чтобы предотвратить изменение данных другими классами.Вы можете определить методы, открытые для других, у которых есть валидации.

Экран (ы) редактирования

Поскольку мои ClassA и ClassB являются двумя отдельными агрегатами, я не планировалесть экран, чтобы обновить их одновременно.Вместо этого у меня есть 2 отдельных контроллера для их представления.

На экране редактирования ClassA я могу предоставить список доступных ClassBs в виде раскрывающегося списка, так как их отношение одно к многим.

Опять нет правильного пути.Все зависит от вашей бизнес-логики.

public ClassAController : AdminControllerBase
{
    private readonly AppDbContext _dbContext;

    public ClassAController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IActionResult Edit(int id)
    {
        // Find the entity by id in the database
        var classAEntity = _dbContext.ClassAs
            .AsNoTracking()
            .SingleOrDefault(x => x.Id == id);
        if (classAEntity == null)
        {
            return NotFound();
        }

        // Find a list of available class Bs
        var availableClassBs = _dbContext.ClassBs
            .AsNoTracking()
            .Where(x => ... your filter ...)
            .OrderBy(x => x.Name)
            .ToDictionary(x => x.Id, x => x.Name);

        // Construct the view model for editing
        var vm = new EditClassAViewModel
        {
            ClassAId = classAEntity.Id,
            Name = classAEntity.Name,
            SelectedClassBId = classAEntity.ClassBId,
            AvailableClassBs = availableClassBs
        };

        return View(vm);
    }
}

Вот модель представления для экрана редактирования.В зависимости от того, что вы хотите редактировать, вы можете создавать свойства для этой цели.Также это правильное место для размещения ваших аннотаций данных, если у вас включена проверка на стороне клиента.

public class EditClassAViewModel
{
    [Required]
    public int ClassAId { get; set; }

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

    [Display(Name = "Class b")]
    public int SelectedClassBId { get; set; }

    public IDictionary<int, string> AvailableClassBs { get; set; }
}

Редактирование ClassA View

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

При отправке

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

Примечание: в моемВ проекте я использовал библиотеку MediatR - отправлял и обрабатывал запросы / команды, библиотека AutoMapper - конвертировал модели туда-сюда и шаблон хранилища, но здесь я просто собрал все вместе, чтобы упростить процесс.

[HttpPost]
public IActionResult Edit(EditClassAViewModel model)
{
    var response = new JsonResponse();
    if (!ModelState.IsValid)
    {
        response.AddModalStateErrors(ModelState);
        return Json(response);
    }

    // Get the ClassA entity from the database and convert the persistence
    // model to your domain model. You could have your repository to do
    // both in one step.
    var classAEntity = _dbContext.ClassAs
        .AsNoTracking()
        .SingleOrDefault(x => x.Id == model.ClassAId);
    if (classAEntity == null)
    {
        response.AddError(...);
        return Json(response);
    }

    // Convert the persistence model to domain model. You could use
    // AutoMapper to do so.
    var classA = new ClassA(...);

    // Class the ClassA domain model UpdateDetails method
    classA.UpdateDetails(...);

    // Convert the domain model back to persistence model
    // and save it to the database. You could have your repository to do
    // both in one step.
    var classAPersistenceModel = ...;

    // Since this persistence model is not tracked by EFCore,
    // you need to fetch the entity again from database by Id and update
    // that entity instead.
    // Again, you could have your repository to do that in one step too.
    classAEntity = _dbContext.ClassAs.Find(classAPersistenceModel.Id);
    if (classAEntity != null)
    {
        _dbContext.Entry(classAEntity).CurrentValues
            .SetValues(classAPersistenceModel);
        _dbContext.SaveChanges();
    }
}

Отказ от ответственности: ДаЯ знаю, что может быть много нового, что я проигнорировал в этом посте: шаблон запроса / команды с использованием IMediatR, проверка с помощью FluentValidation, AutoMaконфигурация pper и шаблон репозитория.Но цель этого поста - просто дать вам понимание моего подхода DDD.XD

0 голосов
/ 30 мая 2018

«Лучший» способ сделать это довольно субъективно, но один из наиболее часто используемых методов для этого - использовать .Include при получении данных, необходимых для вашего представления (как вы уже упоминали, вы делаете выше), затем впросмотрите, что вы можете использовать Html-помощники и форму сообщения, как показано ниже, чтобы отправить отредактированные данные обратно в контроллер:

@model MyApplication.ViewModels.ClassA

@using (Html.BeginForm("Edit", "MyController", FormMethod.Post, new { @class = "form-horizontal", role = "form"))
{
    @Html.EditorFor(x => x.Name)
    @Html.EditorFor(x => x.SubModel.Name)

    <input type="submit" value="Save" />
}

HTML-помощники помогут соответствующим образом отформатировать атрибуты имени html для ваших свойств, чтобы данныеправильно сериализован от вида к контроллеру.

...