C# DDD(域驱动设计),如何处理实体状态更改,以及封装需要处理大量数据的业务规则 公共类人物 { 公共IList特殊出生地; 公共静态只读日期时间重要日期; 公共字符串出生地{get;set;} 公共日期时间出生日期 { 设置 { 如果(出生地!=null&& 值

C# DDD(域驱动设计),如何处理实体状态更改,以及封装需要处理大量数据的业务规则 公共类人物 { 公共IList特殊出生地; 公共静态只读日期时间重要日期; 公共字符串出生地{get;set;} 公共日期时间出生日期 { 设置 { 如果(出生地!=null&& 值,c#,domain-driven-design,C#,Domain Driven Design,这是在我的域模型中封装一个简单规则的尝试。我试图捕捉的规则是:当出于某种原因,我们更新一个人的出生日期(例如,原始用户输入中有错误)时,我们需要检查该人的出生地,并用数据库中的其他值替换它,如果它在我们的数据库中被列为特殊出生地 但是,我在实现它时有两个问题: 此规则修改域实体状态(属性),我需要在用户界面中反映此更改。我的领域模型是POCO。我可以将此逻辑放在ViewModel中,但这是错误的,因为它不是UI逻辑。这是一个重要的域规则,我需要捕获它 我的特殊出生地列表相当大,我不想每次从数据库

这是在我的域模型中封装一个简单规则的尝试。我试图捕捉的规则是:当出于某种原因,我们更新一个人的出生日期(例如,原始用户输入中有错误)时,我们需要检查该人的出生地,并用数据库中的其他值替换它,如果它在我们的数据库中被列为特殊出生地

但是,我在实现它时有两个问题:

  • 此规则修改域实体状态(属性),我需要在用户界面中反映此更改。我的领域模型是POCO。我可以将此逻辑放在ViewModel中,但这是错误的,因为它不是UI逻辑。这是一个重要的域规则,我需要捕获它

  • 我的特殊出生地列表相当大,我不想每次从数据库中获得客户时都填充它。此外,我还需要在符合规定的情况下为出生地找一个替代者。正如我所说的,这个特殊出生地和替代者的列表非常大,并且存储在数据库中


  • 如何在DDD样式中实现我需要的逻辑?

    在性能方面,最好的方法是在数据库中创建一个存储过程,并在属性更改事件上标记entity,以便在将更改提交到数据库时调用它(SaveChanges()调用)
    ObjectContext.ExecuteFunction在这种情况下是您的朋友

    将所有出生地查找和更新的逻辑都放在该存储过程中。确保事务中包含存储过程-以便在更新失败时回滚更改

    编辑:
    很抱歉,没有给出与DDD相关的答案。

    我封装这个问题的方式,即修改跟踪,是使用工作单元模式。我的DDD存储库与一个工作单元相关联,我可以查询工作单元中我从任何存储库中获得的任何一组实体,以查看哪些被修改

    至于大型集合,它似乎是一个只读集合。处理此问题的一种方法是在本地预加载和缓存(如果曾经访问过),然后存储库可以对内存中的版本运行查询。我使用NHibernate,它很容易处理这个案件。如果它太大而无法存储在RAM中(比如100MB或更大),您可能需要对它进行特殊情况存储库查询,以便在数据库上执行
    SpecialBirthPlaces.Contains(诞生地)
    查询(可能在存储过程中,哈!)。您可能希望将
    SpecialBirthPlaces
    表示为实体的存储库,而不仅仅是一个庞大的字符串集合,这将允许“查询”模式使您无需加载整个内容

    在这个冗长的叙述之后,下面是一些例子:

    public class Person
    {
        public IList<String> SpecialBirthPlaces;
        public static readonly DateTime ImportantDate;
        public String BirthPlace {get;set;}
    
        public DateTime BirthDate
        {
            set
            {
                if (BirthPlace!=null && 
                    value < ImportantDate && 
                    SpecialBirthPlaces.Contains(BirthPlace))
                {
                    BirthPlace = DataBase.GetBirthPlaceFor(BirthPlace, value);
                }
            }
        }
    }
    
    我认为“我需要在用户界面中反映这一变化”和“这是我需要捕获的一个重要领域规则”描述了两个不同的问题。显然,第一个问题需要解决;不清楚第二个是否有

    如果域模型的其他部分需要了解此处的更改,那么最好查看域事件(例如)。您还可以在设置出生日期时使用此设置出生地属性,即使这是一个可能很长的操作,也可以异步设置

    否则,让我们看看UI问题。首先,在我的域模型中,我将把每个实体抽象为一个接口。如果没有,那么您可能至少需要将一些属性设置为虚拟属性。我还将使用一个抽象层来生成/返回我的实体,如IoC/factory/repository。我认为这个层超出域模型本身的界限。 现在,我们需要一种机制来通知UI域实体中属性的更改,但当然域模型本身在某种意义上是一个封闭的系统:我们不想引入新成员或行为来满足任何外部关注的需要

    如果我们用一个实现了
    INotifyPropertyChanged
    的实现来装饰有问题的实体,该怎么办?我们可以在我们的存储库中这样做,我们已经建立的存储库在域的边界之外,因此我们不会修改域模型本身,只使用组合将实体与域模型之外的系统所需的功能包装在一起。重申一下,
    出生地的重新计算仍然是域模型的关注点,而UI通知逻辑仍然是域模型之外的关注点

    它看起来像这样:

    public class BirthPlace
    {
        public BirthPlace(String name, Boolean isSpecial = false)
        {
            Name = name;
            IsSpecial = isSpecial
        } 
    
        public String Name { get; private set; }
        public Boolean IsSpecial { get; private set; }
    }
    
    public class Person 
    {
        public static readonly DateTime ImportantDate;
        public BirthPlace BirthPlace { get; set; } 
    
        public DateTime BirthDate 
        { 
            get; private set;
        } 
    
        public void CorrectBirthDate(IRepository<BirthPlace> birthPlaces, DateTime date)
        {
            if (BirthPlace != null && date < ImportantDate && BirthPlace.IsSpecial) 
            { 
                BirthPlace = birthPlaces.GetForDate(date); 
            }
        }
    } 
    

    如果不使用接口,您只需从
    Person
    继承并重写
    BirthDate
    属性,在
    base
    上调用成员,而不是
    \u inner
    以下是一个示例实现。此实现由几个层组成:域层、服务层和表示层。服务层的目的是将域层的功能公开给其他层,例如表示层或web服务。为此,其方法对应于域层可以处理的特定命令。特别是我们有更改生日的命令。此外,此实现使用Udi Dahan版本的。这样做是为了将域实体与与更改生日相关的业务逻辑解耦。这
    public class BirthPlace
    {
        public BirthPlace(String name, Boolean isSpecial = false)
        {
            Name = name;
            IsSpecial = isSpecial
        } 
    
        public String Name { get; private set; }
        public Boolean IsSpecial { get; private set; }
    }
    
    public class Person 
    {
        public static readonly DateTime ImportantDate;
        public BirthPlace BirthPlace { get; set; } 
    
        public DateTime BirthDate 
        { 
            get; private set;
        } 
    
        public void CorrectBirthDate(IRepository<BirthPlace> birthPlaces, DateTime date)
        {
            if (BirthPlace != null && date < ImportantDate && BirthPlace.IsSpecial) 
            { 
                BirthPlace = birthPlaces.GetForDate(date); 
            }
        }
    } 
    
    public class NotifyPerson : IPerson, INotifyPropertyChanged
    {
        readonly IPerson _inner;
    
        public NotifyPerson(IPerson inner) // repository puts the "true" domain entity here
        {
            _inner = inner;
        }
    
        public DateTime BirthDate
        {
            set 
            {
                if(value == _inner.BirthDate)
                    return;
    
                var previousBirthPlace = BirthPlace;
                _inner.BirthDate = value;
                Notify("BirthDate");
    
                if(BirthPlace != previousBirthPlace) 
                    Notify("BirthPlace");
            }
        }
    
        void Notify(string property)
        {
            var handler = PropertyChanged;
            if(handler != null) handler(this, new PropertyChangedEventArgs(property));
        }
    }
    
    /// <summary>
    /// This is your main entity, while it may seem anemic, it is only because 
    /// it is simplistic.
    /// </summary>
    class Person
    {
        public string Id { get; set; }
        public string BirthPlace { get; set; }
    
        DateTime birthDate;
    
        public DateTime BirthDate
        {
            get { return this.birthDate; }
            set
            {
                if (this.birthDate != value)
                {
                    this.birthDate = value;
                    DomainEvents.Raise(new BirthDateChangedEvent(this.Id));
                }
            }
        }
    }
    
    /// <summary>
    /// Udi Dahan's implementation.
    /// </summary>
    static class DomainEvents
    {
        public static void Raise<TEvent>(TEvent e) where TEvent : IDomainEvent
        {
        }
    }
    
    interface IDomainEvent { }
    
    /// <summary>
    /// This is the interesting domain event which interested parties subscribe to 
    /// and handle in special ways.
    /// </summary>
    class BirthDateChangedEvent : IDomainEvent
    {
        public BirthDateChangedEvent(string personId)
        {
            this.PersonId = personId;
        }
    
        public string PersonId { get; private set; }
    }
    
    /// <summary>
    /// This can be associated to a Unit of Work.
    /// </summary>
    interface IPersonRepository
    {
        Person Get(string id);
        void Save(Person person);
    }
    
    /// <summary>
    /// This can implement caching for performance.
    /// </summary>
    interface IBirthPlaceRepository
    {
        bool IsSpecial(string brithPlace);
        string GetBirthPlaceFor(string birthPlace, DateTime birthDate);
    }
    
    interface IUnitOfWork : IDisposable
    {
        void Commit();
    }
    
    static class UnitOfWork
    {
        public static IUnitOfWork Start()
        {
            return null;
        }
    }
    
    class ChangeBirthDateCommand
    {
        public string PersonId { get; set; }
        public DateTime BirthDate { get; set; }
    }
    
    /// <summary>
    /// This is the application layer service which exposes the functionality of the domain 
    /// to the presentation layer.
    /// </summary>
    class PersonService
    {
        readonly IPersonRepository personDb;
    
        public void ChangeBirthDate(ChangeBirthDateCommand command)
        {
            // The service is a good place to initiate transactions, security checks, etc.
            using (var uow = UnitOfWork.Start())
            {
                var person = this.personDb.Get(command.PersonId);
                if (person == null)
                    throw new Exception();
    
                person.BirthDate = command.BirthDate;
    
                // or business logic can be handled here instead of having a handler.
    
                uow.Commit();
            }
        }
    }
    
    /// <summary>
    /// This view model is part of the presentation layer.
    /// </summary>
    class PersonViewModel
    {
        public PersonViewModel() { }
    
        public PersonViewModel(Person person)
        {
            this.BirthPlace = person.BirthPlace;
            this.BirthDate = person.BirthDate;
        }
    
        public string BirthPlace { get; set; }
        public DateTime BirthDate { get; set; }
    }
    
    /// <summary>
    /// This is part of the presentation layer.
    /// </summary>
    class PersonController
    {
        readonly PersonService personService;
        readonly IPersonRepository personDb;
    
        public void Show(string personId)
        {
            var person = this.personDb.Get(personId);
            var viewModel = new PersonViewModel(person);
            // UI framework code here.
        }
    
        public void HandleChangeBirthDate(string personId, DateTime birthDate)
        {
            this.personService.ChangeBirthDate(new ChangeBirthDateCommand { PersonId = personId, BirthDate = birthDate });
            Show(personId);
        }
    }
    
    interface IHandle<TEvent> where TEvent : IDomainEvent
    {
        void Handle(TEvent e);
    }
    
    /// <summary>
    /// This handler contains the business logic associated with changing birthdates. This logic may change
    /// and may depend on other factors.
    /// </summary>
    class BirthDateChangedBirthPlaceHandler : IHandle<BirthDateChangedEvent>
    {
        readonly IPersonRepository personDb;
        readonly IBirthPlaceRepository birthPlaceDb;
        readonly DateTime importantDate;
    
        public void Handle(BirthDateChangedEvent e)
        {
            var person = this.personDb.Get(e.PersonId);
            if (person == null)
                throw new Exception();
    
            if (person.BirthPlace != null && person.BirthDate < this.importantDate)
            {
                if (this.birthPlaceDb.IsSpecial(person.BirthPlace))
                {
                    person.BirthPlace = this.birthPlaceDb.GetBirthPlaceFor(person.BirthPlace, person.BirthDate);
                    this.personDb.Save(person);
                }
            }
        }
    }