使用NHibernate实现存储库
ORM框架不是存储库。存储库是一种架构模式,而ORM是将数据模型表示成对象模型的一种手段。
存储库可以使用ORM来协助充当领域模型和数据模型之间的中介。
NHibernate允许在不损坏或只少量损坏领域模型的情况下直接将领域模型映射到数据模型。
下面就以一个在线拍卖的例子来说明。
自动竞价逻辑:
- 当一个竞标者出价时,他会输入他愿意为该物品支付的最大金额。不过,他的实际出价会是超过上一次出价或起拍价所需的最小金额。
 - 当第二个竞标者出价时,一个自动竞标器会代表前一个竞标者出价,直到达到其最大金额或足以击败第二个竞标者时才停止。
 - 如果第二个竞标者超过了前一个竞标者的最大出价,则会通知前一个竞标者他的出价已经被超过了。
 - 如果第二个竞标者的出价与前一个竞标者的最大出价相同,则前一个竞标者仍旧是本次拍卖的赢家,因为他先出价。
 
实体基类:每个实体需要提供一个Id来跟踪,NHibernate持久化实体时会检查Version以确保版本是一致的
public abstract class Entity<TId>
    {
        public TId Id { get; protected set; }
        public int Version { get; private set; }
    }
领域事件基础架构类:
public static class DomainEvents
    {
        [ThreadStatic]
        private static List<Delegate> _actions;
        private static List<Delegate> Actions
        {
            get
            {
                if (_actions == null)
                {
                    _actions = new List<Delegate>();
                }
                return _actions;
            }
        }
        public static IDisposable Register<T>(Action<T> callback)
        {
            Actions.Add(callback);
            return new DomainEventRegistrationRemover(
                () => Actions.Remove(callback)
                );
        }
        public static void Raise<T>(T eventArgs)
        {
            foreach (Delegate action in Actions)
            {
                (action as Action<T>)?.Invoke(eventArgs);
            }
        }
        private sealed class DomainEventRegistrationRemover : IDisposable
        {
            private readonly Action _callOnDispose;
            public DomainEventRegistrationRemover(Action toCall)
            {
                _callOnDispose = toCall;
            }
            public void Dispose()
            {
                _callOnDispose();
            }
        }
    }
值对象基类:
public abstract class ValueObject<T> where T : ValueObject<T>
    {
        protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();
        public override bool Equals(object other)
        {
            return Equals(other as T);
        }
        public bool Equals(T other)
        {
            if (other == null)
            {
                return false;
            }
            return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
        }
        public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
        {
            return Equals(left, right);
        }
        public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
        {
            return !(left == right);
        }
        public override int GetHashCode()
        {
            ;
            foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
                hash = hash *  + (obj ==  : obj.GetHashCode());
            return hash;
        }
    }
资金值对象:
public class Money : ValueObject<Money>,IComparable<Money>
    {
        protected decimal Value { get; set; }
        public Money():this(0m)
        {
        }
        public Money(decimal value)
        {
            ThrowExceptionIfNotValid(value);
            this.Value = value;
        }
        private void ThrowExceptionIfNotValid(decimal value)
        {
            )
            {
                throw new MoreThanTwoDecimalPlacesInMoneyValueException();
            }
            )
                throw new MoneyCannotBeANegativeValueException();
        }
        public Money Add(Money money)
        {
            return new Money(Value + money.Value);
        }
        public bool IsGreaterThan(Money money)
        {
            return this.Value > money.Value;
        }
        public bool IsGreaterThanOrEqualTo(Money money)
        {
            return this.Value > money.Value || this.Equals(money);
        }
        public bool IsLessThanOrEqualTo(Money money)
        {
            return this.Value < money.Value || this.Equals(money);
        }
        public override string ToString()
        {
            return string.Format("{0}", Value);
        }
        public int CompareTo(Money other)
        {
            return this.Value.CompareTo(other.Value);
        }
        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
        {
            return new List<object>() { Value };
        }
    }
领域异常:
public class MoneyCannotBeANegativeValueException : Exception
    {
    }
public class MoreThanTwoDecimalPlacesInMoneyValueException : Exception
    {
    }
报价值对象:
public class Offer : ValueObject<Offer>
    {
        public Offer(Guid bidderId,Money maximumBid,DateTime timeOfOffer)
        {
            if(bidderId==Guid.Empty)
            {
                throw new ArgumentNullException("BidderId cannot be null");
            }
            if(maximumBid==null)
            {
                throw new ArgumentNullException("MaximumBid cannot be null");
            }
            if(timeOfOffer==DateTime.MinValue)
            {
                throw new ArgumentNullException("Time of Offer must have a value");
            }
            Bidder = bidderId;
            MaximumBid = maximumBid;
            TimeOfOffer = timeOfOffer;
        }
        public Guid Bidder { get; private set; }
        public Money MaximumBid { get; private set; }
        public DateTime TimeOfOffer { get; private set; }
        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
        {
            return new List<object>()
            {
                Bidder,MaximumBid,TimeOfOffer
            };
        }
    }
价格值对象:
public class Price : ValueObject<Price>
    {
        private Price() { }
        public Price(Money amount)
        {
            if (amount == null)
                throw new ArgumentNullException("Amount cannot be null");
            Amount = amount;
        }
        public Money Amount { get; private set; }
        public Money BidIncrement()
        {
            if(Amount.IsGreaterThanOrEqualTo(new Money(0.01m))&&Amount.IsLessThanOrEqualTo(new Money(0.99m)))
            {
                return Amount.Add(new Money(0.05m));
            }
            if (Amount.IsGreaterThanOrEqualTo(new Money(1.00m)) && Amount.IsLessThanOrEqualTo(new Money(4.99m)))
            {
                return Amount.Add(new Money(0.20m));
            }
            if (Amount.IsGreaterThanOrEqualTo(new Money(5.00m)) && Amount.IsLessThanOrEqualTo(new Money(14.99m)))
            {
                return Amount.Add(new Money(0.50m));
            }
            return Amount.Add(new Money(1.00m));
        }
        public bool CanBeExceededBy(Money offer)
        {
            return offer.IsGreaterThanOrEqualTo(BidIncrement());
        }
        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
        {
            return new List<object>() { Amount };
        }
    }
中标值对象:
public class WinningBid : ValueObject<WinningBid>
    {
        private WinningBid() { }
        public WinningBid(Guid bidder,Money maximumBid,Money bid,DateTime timeOfBid)
        {
            if (bidder == Guid.Empty)
                throw new ArgumentNullException("Bidder cannot be null");
            if (maximumBid == null)
                throw new ArgumentNullException("MaximumBid cannot be null");
            if (timeOfBid == DateTime.MinValue)
                throw new ArgumentNullException("TimeOfBid must have a value");
            Bidder = bidder;
            this.MaximumBid = maximumBid;
            this.TimeOfBid = timeOfBid;
            this.CurrentAuctionPrice = new Price(bid);
        }
        public WinningBid RaiseMaximumBidTo(Money newAmount)
        {
            if (newAmount.IsGreaterThan(MaximumBid))
                return new WinningBid(Bidder, newAmount, CurrentAuctionPrice.Amount, DateTime.Now);
            else
                throw new ApplicationException("Maximum bid increase must be larger than current maximum bid.");
        }
        public bool WasMadeBy(Guid bidder)
        {
            return Bidder.Equals(bidder);
        }
        public bool CanBeExceededBy(Money offer)
        {
            return CurrentAuctionPrice.CanBeExceededBy(offer);
        }
        public bool HasNotReachedMaximumBid()
        {
            return MaximumBid.IsGreaterThan(CurrentAuctionPrice.Amount);
        }
        public Guid Bidder { get; private set; }
        public Money MaximumBid { get; private set; }
        public DateTime TimeOfBid { get; private set; }
        public Price CurrentAuctionPrice { get; private set; }
        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
        {
            return new List<object>() { Bidder, MaximumBid, TimeOfBid, CurrentAuctionPrice };
        }
    }
自动出价领域服务:
public class AutomaticBidder
    {
        public IEnumerable<WinningBid> GenerateNextSequenceOfBidsAfter(Offer offer,WinningBid currentWinningBid)
        {
            var bids = new List<WinningBid>();
            if(currentWinningBid.MaximumBid.IsGreaterThanOrEqualTo(offer.MaximumBid))
            {
                var bidFromOffer = new WinningBid(offer.Bidder, offer.MaximumBid, offer.MaximumBid, offer.TimeOfOffer);
                bids.Add(bidFromOffer);
                bids.Add(CalculateNextBid(bidFromOffer, new Offer(currentWinningBid.Bidder, currentWinningBid.MaximumBid, currentWinningBid.TimeOfBid)));
            }
            else
            {
                var currentBiddersLastBid = new WinningBid(currentWinningBid.Bidder, currentWinningBid.MaximumBid, currentWinningBid.MaximumBid, currentWinningBid.TimeOfBid);
                bids.Add(currentBiddersLastBid);
                bids.Add(CalculateNextBid(currentBiddersLastBid, offer));
            }
            return bids;
        }
        private WinningBid CalculateNextBid(WinningBid winningBid, Offer offer)
        {
            WinningBid bid;
            if(winningBid.CanBeExceededBy(offer.MaximumBid))
            {
                bid = new WinningBid(offer.Bidder, offer.MaximumBid, winningBid.CurrentAuctionPrice.BidIncrement(), offer.TimeOfOffer);
            }
            else
            {
                bid = new WinningBid(offer.Bidder, offer.MaximumBid, offer.MaximumBid, offer.TimeOfOffer);
            }
            return bid;
        }
    }
出价值对象:
public class BidPlaced
    {
        public Guid AuctionId { get; private set; }
        public Guid Bidder { get; private set; }
        public Money AmountBid { get; private set; }
        public DateTime TimeOfMemberBid { get; private set; }
        public BidPlaced(Guid auctionId,Guid bidderId,Money amountBid,DateTime timeOfBid)
        {
            if (auctionId == Guid.Empty)
                throw new ArgumentNullException("Auction Id cannot be null");
            if (bidderId == Guid.Empty)
                throw new ArgumentNullException("Bidder Id cannot be null");
            if (amountBid == null)
                throw new ArgumentNullException("AmountBid cannot be null");
            if (timeOfBid == DateTime.MinValue)
                throw new ArgumentNullException("TimeOfBid must have a value");
            this.AuctionId = auctionId;
            this.Bidder = bidderId;
            this.AmountBid = amountBid;
            this.TimeOfMemberBid = timeOfBid;
        }
    }
出价被超过值对象:
public class OutBid
    {
        public Guid AuctionId { get; private set; }
        public Guid Bidder { get; private set; }
        public OutBid(Guid auctionId,Guid bidderId)
        {
            if (auctionId == Guid.Empty)
                throw new ArgumentNullException("Auction Id cannot be null");
            if (bidderId == Guid.Empty)
                throw new ArgumentNullException("Bidder Id cannot be null");
            this.AuctionId = auctionId;
            this.Bidder = bidderId;
        }
    }
拍卖实体:
public class Auction:Entity<Guid>
    {
        private Money StartingPrice { get; set; }
        private WinningBid WinningBid { get; set; }
        private DateTime EndsAt { get; set; }
        private Auction() { }
        public Auction(Guid id,Money startingMoney,DateTime endsAt)
        {
            if (id == Guid.Empty)
                throw new ArgumentNullException("Auction Id cannot be null");
            if (startingMoney == null)
                throw new ArgumentNullException("Starting Price cannot be null");
            if (endsAt == DateTime.MinValue)
                throw new ArgumentNullException("EndsAt must have a value");
            this.Id = id;
            this.StartingPrice = startingMoney;
            this.EndsAt = endsAt;
        }
        private bool StillInProgress(DateTime currentTime)
        {
            return (EndsAt > currentTime);
        }
        public void PlaceBidFor(Offer offer,DateTime currentTime)
        {
            if(StillInProgress(currentTime))
            {
                if(FirstOffer())
                {
                    PlaceABidForTheFirst(offer);
                }
                else if(BidderIsIncreasingMaximumBidToNew(offer))
                {
                    WinningBid = WinningBid.RaiseMaximumBidTo(offer.MaximumBid);
                }
                else if(WinningBid.CanBeExceededBy(offer.MaximumBid))
                {
                    var newBids = new AutomaticBidder().GenerateNextSequenceOfBidsAfter(offer, WinningBid);
                    foreach(var bid in newBids)
                    {
                        Place(bid);
                    }
                }
            }
        }
        private bool BidderIsIncreasingMaximumBidToNew(Offer offer)
        {
            return WinningBid.WasMadeBy(offer.Bidder) && offer.MaximumBid.IsGreaterThan(WinningBid.MaximumBid);
        }
        private bool FirstOffer()
        {
            return WinningBid == null;
        }
        private void PlaceABidForTheFirst(Offer offer)
        {
            if (offer.MaximumBid.IsGreaterThanOrEqualTo(StartingPrice))
                Place(new WinningBid(offer.Bidder, offer.MaximumBid, StartingPrice, offer.TimeOfOffer));
        }
        private void Place(WinningBid newBid)
        {
            if (!FirstOffer() && WinningBid.WasMadeBy(newBid.Bidder))
                DomainEvents.Raise(new OutBid(Id, WinningBid.Bidder));
            WinningBid = newBid;
            DomainEvents.Raise(new BidPlaced(Id, newBid.Bidder, newBid.CurrentAuctionPrice.Amount, newBid.TimeOfBid));
        }
    }
拍卖存储库接口:
public interface IAuctionRepository
    {
        void Add(Auction auction);
        Auction FindBy(Guid id);
    }
竞标值对象:
public class Bid : ValueObject<Bid>
    {
        private Guid Id { get; set; }
        public Guid AuctionId { get; private set; }
        public Guid Bidder { get; private set; }
        public Money AmountBid { get; private set; }
        public DateTime TimeOfBid { get; private set; }
        private Bid() { }
        public Bid(Guid auctionId,Guid bidderId,Money amountBid,DateTime timeOfBid)
        {
            if (auctionId == Guid.Empty)
                throw new ArgumentNullException("Auction Id cannot be null");
            if (bidderId == Guid.Empty)
                throw new ArgumentNullException("Bidder Id cannot be null");
            if (amountBid == null)
                throw new ArgumentNullException("AmountBid cannot be null");
            if (timeOfBid == DateTime.MinValue)
                throw new ArgumentNullException("TimeOfBid must have a value");
            this.AuctionId = auctionId;
            this.Bidder = bidderId;
            this.AmountBid = amountBid;
            this.TimeOfBid = timeOfBid;
        }
        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
        {
            return new List<object>() { Bidder, AuctionId, TimeOfBid, AmountBid };
        }
    }
通过这些实体和值对象可以看到:它们的设置属性都是私有的,这是为了保护领域模型不被外部更改。
需要被持久化的实体和值对象还需要一个无参构造函数,如Auction和Bid,这是NHibernate的限制条件,该构造函数可以是私有的,因而对模型不会造成影响。
出价历史存储库接口:
public interface IBidHistoryRepository
    {
        int NoOfBidsFor(Guid auctionId);
        void Add(Bid bid);
    }
新拍卖请求命令:这里是一个DTO对象
public class NewAuctionRequest
    {
        public decimal StartingPrice { get; set; }
        public DateTime EndsAt { get; set; }
    }
创建一次拍卖的应用程序服务:这里可以看到整个工作单元由ISession提供,由存储库提供查询和保存服务,但事务的提交在应用程序中,而不是在存储库中。
public class CreateAuction
    {
        private IAuctionRepository _auctionRepository;
        private ISession _unitOfWork;
        public CreateAuction(IAuctionRepository auctionRepository,ISession unitOfWork)
        {
            this._auctionRepository = auctionRepository;
            this._unitOfWork = unitOfWork;
        }
        public Guid Create(NewAuctionRequest command)
        {
            var auctionId = Guid.NewGuid();
            var startingPrice = new Money(command.StartingPrice);
            using (ITransaction transaction = _unitOfWork.BeginTransaction())
            {
                _auctionRepository.Add(new Auction(auctionId, startingPrice, command.EndsAt));
                transaction.Commit();
            }
            return auctionId;
        }
    }
用于时钟类的接口:
 public interface IClock
    {
        DateTime Time();
    }
时钟接口的实现:
public class SystemClock : IClock
    {
        public DateTime Time()
        {
            return DateTime.Now;
        }
    }
在拍卖上出价的命令:只有在transaction commit后数据才会写入数据库
public class BidOnAuction
    {
        private IAuctionRepository _auctionRepository;
        private IBidHistoryRepository _bidHistoryRepository;
        private ISession _unitOfWork;
        private IClock _clock;
        public BidOnAuction(IAuctionRepository auctionRepository,IBidHistoryRepository bidHistoryRepository,ISession unitOfWork,IClock clock)
        {
            _auctionRepository = auctionRepository;
            _bidHistoryRepository = bidHistoryRepository;
            _unitOfWork = unitOfWork;
            _clock = clock;
        }
        public void Bid(Guid auctionId,Guid memberId,decimal amount)
        {
            try
            {
                using (ITransaction transaction = _unitOfWork.BeginTransaction())
                {
                    using (DomainEvents.Register(OutBid()))
                    using (DomainEvents.Register(BidPlaced()))
                    {
                        var auction = _auctionRepository.FindBy(auctionId);
                        var bidAmount = new Money(amount);
                        auction.PlaceBidFor(new Offer(memberId, bidAmount, _clock.Time()), _clock.Time());
                    }
                    transaction.Commit();
                }
            }
            catch (StaleObjectStateException ex)
            {
                _unitOfWork.Clear();
                Bid(auctionId, memberId, amount);
            }
            catch(Exception ex)
            {
                var message = ex.Message;
            }
        }
        private Action<BidPlaced> BidPlaced()
        {
            return (BidPlaced e) =>
            {
                var bidEvent = new Bid(e.AuctionId, e.Bidder, e.AmountBid, e.TimeOfMemberBid);
                _bidHistoryRepository.Add(bidEvent);
            };
        }
        private Action<OutBid> OutBid()
        {
            return (OutBid e) =>
            {
            };
        }
    }
用于拍卖类的NHibernate XML映射:Auction对应的表为Auctions,值对象分别对应表中的字段
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="DDDPPP.NHibernateExample.Application.Model.Auction"
        assembly="DDDPPP.NHibernateExample.Application">
  <class name="Auction" table="Auctions" lazy="false" >
    <id name="Id" column="Id" type="Guid">
    </id>
    <version name="/>
    <component name="StartingPrice" class="Money">
      <property name="Value" column="StartingPrice" not-null="true"/>
    </component>
    <property name="EndsAt" column="AuctionEnds" not-null="true"/>
    <component name="WinningBid" class="WinningBid">
      <property name="Bidder" column="BidderMemberId" not-null="false"/>
      <property name="TimeOfBid" column="TimeOfBid" not-null="false"/>
      <component name="MaximumBid" class="Money">
        <property name="Value" column="MaximumBid" not-null="false"/>
      </component>
      <component name="CurrentAuctionPrice" class="Price">
        <component name="Amount" class="Money">
          <property name="Value" column="CurrentPrice" not-null="false"/>
        </component>
      </component>
    </component>
  </class>
</hibernate-mapping>
用于出价历史的NHibernate XML映射:Bid对应的表为BidHistory,值对象AmountBid被映射到BidHistory表中的Bid字段
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="DDDPPP.NHibernateExample.Application.Model.BidHistory"
        assembly="DDDPPP.NHibernateExample.Application">
  <class name="Bid" table="BidHistory" lazy="false" >
    <id name="Id" column="Id" type="Guid">
      <generator class="guid"/>
    </id>
    <property name="AuctionId" column="AuctionId" not-null="false"/>
    <property name="Bidder" column="BidderId" not-null="false"/>
    <component name="AmountBid" class="DDDPPP.NHibernateExample.Application.Model.Auction.Money">
      <property name="Value" column="Bid" not-null="false"/>
    </component>
    <property name="TimeOfBid" column="TimeOfBid" not-null="false"/>
  </class>
</hibernate-mapping>
这两个hbm.xml文件需要在生成操作更改成嵌入的资源

出价历史存储库实现:提供保存和查询数量功能
public class BidHistoryRepository : IBidHistoryRepository
    {
        private readonly ISession _session;
        public BidHistoryRepository(ISession session)
        {
            _session = session;
        }
        public void Add(Bid bid)
        {
            _session.Save(bid);
        }
        public int NoOfBidsFor(Guid auctionId)
        {
            var sql = string.Format("select count(1) from BidHistory where AuctionId='{0}'", auctionId);
            var query = _session.CreateSQLQuery(sql);
            var result = query.UniqueResult();
            return Convert.ToInt32(result);
        }
    }
拍卖存储库实现:提供保存和查找功能
public class AuctionRepository : IAuctionRepository
    {
        private readonly ISession _session;
        public AuctionRepository(ISession session)
        {
            _session = session;
        }
        public void Add(Auction auction)
        {
            _session.Save(auction);
        }
        public Auction FindBy(Guid id)
        {
            return _session.Get<Auction>(id);
        }
    }
数据库创建脚本:
use AuctionExample go set ansi_nulls on go set quoted_identifier on go create table dbo.BidHistory( AuctionId uniqueidentifier not null, BidderId uniqueidentifier not null, Bid numeric(,) not null, TimeOfBid datetime not null, Id uniqueidentifier not null, constraint PK_BidHistory primary key clustered ( Id asc ) with (pad_index=off,statistics_norecompute=off,ignore_dup_key=off,allow_row_locks=on,allow_page_locks=on ) on [primary] ) on [primary] go set ansi_nulls on go set quoted_identifier on go create table dbo.Auctions( Id uniqueidentifier not null, StartingPrice ,) not null, BidderMemberId uniqueidentifier null, TimeOfBid datetime null, MaximumBid ,) null, CurrentPrice ,) null, AuctionEnds datetime not null, Version int not null, constraint PK_Auctions primary key clustered ( Id asc ) with (pad_index=off,statistics_norecompute=off,ignore_dup_key=off,allow_row_locks=on,allow_page_locks=on ) on [primary] ) on [primary] go
拍卖状态类:查询服务不要使用领域对象,使用简单视图模型即可
public class AuctionStatus
    {
        public Guid Id { get; set; }
        public decimal CurrentPrice { get; set; }
        public DateTime AuctionEnds { get; set; }
        public Guid WinningBidderId { get; set; }
        public int NumberOfBids { get; set; }
        public TimeSpan TimeRemaining { get; set; }
    }
拍卖查询类:这里SQL中的字段名需要与类中的一致,如CurrentPrice,否则NHibernate会报错,查询使用原生SQL就行,没必要用存储库处理报告问题
public class AuctionStatusQuery
    {
        private readonly ISession _session;
        private readonly IBidHistoryRepository _bidHistory;
        private readonly IClock _clock;
        public AuctionStatusQuery(ISession session,IBidHistoryRepository bidHistory,IClock clock)
        {
            this._session = session;
            this._bidHistory = bidHistory;
            this._clock = clock;
        }
        public AuctionStatus AuctionStatus(Guid auctionId)
        {
            var status = _session.CreateSQLQuery(string.Format(
                "select Id,CurrentPrice,Biddermemberid as WinningBidderId, " +
                "AuctionEnds from auctions where Id='{0}'", auctionId)).SetResultTransformer(
                Transformers.AliasToBean<AuctionStatus>()).UniqueResult<AuctionStatus>();
            status.TimeRemaining = TimeRemaining(status.AuctionEnds);
            status.NumberOfBids = _bidHistory.NoOfBidsFor(auctionId);
            return status;
        }
        private TimeSpan TimeRemaining(DateTime auctionEnds)
        {
            if (_clock.Time() < auctionEnds)
                return auctionEnds.Subtract(_clock.Time());
            else
                return new TimeSpan();
        }
    }
出价信息数据传输对象:这里不希望公开领域对象,所以要创建一个特定DTO
public class BidInformation
    {
        public Guid Bidder { get; set; }
        public decimal AmountBid { get; set; }
        public string currency { get; set; }
        public DateTime TimeOfBid { get; set; }
    }
出价历史查询服务:
public class BidHistoryQuery
    {
        private readonly ISession _session;
        public BidHistoryQuery(ISession session)
        {
            _session = session;
        }
        public IEnumerable<BidInformation> BidHistoryFor(Guid auctionId)
        {
            var status = _session.CreateSQLQuery(string.Format(
                "select BidderId as Bidder,bid as AmountBid,TimeOfBid " +
                "from bidhistory where auctionId='{0}' order by bid desc,timeofbid asc", auctionId))
                .SetResultTransformer(Transformers.AliasToBean<BidInformation>());
            return status.List<BidInformation>();
        }
    }
NHibernate 配置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
  </configSections>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory name="NHibernate.Test">
      <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
      <property name="connection.connection_string">
        Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AuctionExample;Integrated Security=True;Connect Timeout=;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False
      </property>
      <property name=</property>
      <property name="show_sql">false</property>
      <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
      <property name=</property>
      <property name=, , yes 'Y', no 'N'</property>
    </session-factory>
  </hibernate-configuration>
</configuration>
使用StructureMap注入:
public static class Bootstrapper
    {
        public static Container ObjectFactory { get; set; }
        public static void Startup()
        {
            Configuration config = new Configuration();
            config.Configure();
            config.AddAssembly("DDDPPP.NHibernateExample.Application");
            var sessionFactory = config.BuildSessionFactory();
            ObjectFactory = new Container(_ =>
            {
                _.For<IAuctionRepository>().Use<AuctionRepository>();
                _.For<IBidHistoryRepository>().Use<BidHistoryRepository>();
                _.For<IClock>().Use<SystemClock>();
                _.For<ISessionFactory>().Use(sessionFactory);
                _.For<ISession>().Use(sessionFactory.OpenSession());
            });
        }
    }
客户端程序:
class Program
    {
        private static Dictionary<Guid, string> members = new Dictionary<Guid, string>();
        static void Main(string[] args)
        {
            Bootstrapper.Startup();
            var memberIdA = Guid.NewGuid();
            var memberIdB = Guid.NewGuid();
            members.Add(memberIdA, "Ted");
            members.Add(memberIdB, "Rob");
            var auctionId = CreateAuction();
            Bid(auctionId, memberIdA, 10m);
            Bid(auctionId, memberIdB, 1.49m);
            Bid(auctionId, memberIdB, 10.01m);
            Bid(auctionId, memberIdB, 12.00m);
            Bid(auctionId, memberIdA, 12.00m);
        }
        public static Guid CreateAuction()
        {
            var createAuctionService = Bootstrapper.ObjectFactory.GetInstance<CreateAuction>();
            var newAuctionRequest = new NewAuctionRequest();
            newAuctionRequest.StartingPrice = 0.99m;
            newAuctionRequest.EndsAt = DateTime.Now.AddDays();
            var auctionId = createAuctionService.Create(newAuctionRequest);
            return auctionId;
        }
        public static void Bid(Guid auctionId, Guid memberId, decimal amount)
        {
            var bidOnAuctionService = Bootstrapper.ObjectFactory.GetInstance<BidOnAuction>();
            bidOnAuctionService.Bid(auctionId, memberId, amount);
            PrintStatusOfAuctionBy(auctionId);
            PrintBidHistoryOf(auctionId);
            Console.WriteLine("Hit any key to continue");
            Console.ReadLine();
        }
        public static void PrintStatusOfAuctionBy(Guid auctionId)
        {
            var auctionSummaryQuery = Bootstrapper.ObjectFactory.GetInstance<AuctionStatusQuery>();
            var status = auctionSummaryQuery.AuctionStatus(auctionId);
            Console.WriteLine("No Of Bids: " + status.NumberOfBids);
            Console.WriteLine("Current Bid: " + status.CurrentPrice.ToString("##.##"));
            Console.WriteLine("Winning Bidder: " + FindNameOfBidderWith(status.WinningBidderId));
            Console.WriteLine("Time Remaining: " + status.TimeRemaining);
            Console.WriteLine();
        }
        public static void PrintBidHistoryOf(Guid auctionId)
        {
            var bidHistoryQuery = Bootstrapper.ObjectFactory.GetInstance<BidHistoryQuery>();
            var status = bidHistoryQuery.BidHistoryFor(auctionId);
            Console.WriteLine("Bids..");
            foreach (var bid in status)
                Console.WriteLine(FindNameOfBidderWith(bid.Bidder) + "\t - " + bid.AmountBid.ToString("G") + "\t at " + bid.TimeOfBid);
            Console.WriteLine("------------------------------");
            Console.WriteLine();
        }
        public static string FindNameOfBidderWith(Guid id)
        {
            if (members.ContainsKey(id))
                return members[id];
            else
                return string.Empty;
        }
    }
运行效果:

运行完成后可以查看数据库:


如果NHibernate配置如下:
<property name="show_sql">true</property>
会输出SQL:

整体的结构如图:



上面是领域层类库,下面是表现层控制台应用程序。
领域层分为模型、基础设施、应用程序服务。
实体、值对象、存储库接口、领域错误定义在模型层。
用例、查询定义在应用程序服务中。
存储库实现、领域事件基类、实体基类、值对象基类、ORM配置项等放在基础设施层中。
使用NHibernate实现存储库的更多相关文章
- [leetcode]380. Insert Delete GetRandom O(1)设计数据结构,实现存,删,随机取的时间复杂度为O(1)
		
题目: Design a data structure that supports all following operations in average O(1) time.1.insert(val ...
 - 用邻接表或vector实现存边以及具体如何调用[模板]
		
存边: 对于指针实现的邻接表: struct edge{ int from,next,to,w; }E[maxn]; int head[maxn],tot=0;//head初始化为-1: void a ...
 - vector(实现存图)
		
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #i ...
 - java TreeSet 实现存自定义不可重复数据
		
本文主要是介绍一下java集合中的比较重要的Set接口下的可实现类TreeSet TreeSet类,底层用二叉树的数据结构 * 集合中以有序的方式插入和抽取元素. * 添加到TreeSet中的元素必须 ...
 - java+mybatis实现一个简单的银行系统,实现存取款与账户查询
		
先创建数据库和表,使用的是MySQL数据库. create database mybatis; use mybatis; CREATE TABLE `accountdo` ( `id` varchar ...
 - 结合实体框架(代码优先)、工作单元测试、Web API、ASP. net等,以存储库设计模式开发示例项目。NET MVC 5和引导
		
介绍 这篇文章将帮助你理解在库模式.实体框架.Web API.SQL Server 2012.ASP中的工作单元测试的帮助下设计一个项目.净MVC应用程序.我们正在开发一个图书实体和作者专用的样例图书 ...
 - ABP框架学习
		
一.总体与公共结构 1,ABP配置 2,多租户 3,ABP Session 4,缓存 5,日志 6,设置管理 7,Timing 8,ABPMapper 9,发送电子邮件 二.领域层 10,实体 11, ...
 - abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)
		
Introduction With AspNet MVC Web API EntityFramework and AngularJS 地址:https://aspnetboilerplate.com/ ...
 - [半翻]  设计面向DDD的微服务
		
这篇文章行文结构对照微软博客, 结合本人意译和多年实践的回顾思考形成此次读书笔记. Domian-driven Design 领域-驱动-设计(DDD)提倡基于(用例相关的现实业务)进行建模. 1. ...
 
随机推荐
- hdu 5862 Counting Intersections
			
传送门:hdu 5862 Counting Intersections 题意:对于平行于坐标轴的n条线段,求两两相交的线段对有多少个,包括十,T型 官方题解:由于数据限制,只有竖向与横向的线段才会产生 ...
 - Could not open INSTALL.LOG file
			
今天卸载MailEnable时弹出这个提示,用360这样的工具卸载问题依旧,折腾了大半天没解决 UNWISE Could not open INSTALL.LOG file 本人有强迫症,虽然这个软件 ...
 - HDU 1828 / POJ 1177 Picture --线段树求矩形周长并
			
题意:给n个矩形,求矩形周长并 解法:跟求矩形面积并差不多,不过线段树节点记录的为: len: 此区间线段长度 cover: 此区间是否被整个覆盖 lmark,rmark: 此区间左右端点是否被覆盖 ...
 - 解决ios下的微信打开的页面背景音乐无法自动播放
			
后面的项目发现,还有两个坑,需要注意下: ·本文的解决方案的核心是利用了 微信/易信 在ready的时候会有个 WeixinJSBridgeReady/YixinJSBridgeReady事件,通过监 ...
 - C# Thread.Join()用法的理解  转
			
指在一线程里面调用另一线程join方法时,表示将本线程阻塞直至另一线程终止时再执行 比如 1using System; 2 3namespace TestThreadJoin 4{ 5 class P ...
 - 语义化的html结构的好处
			
HTML是提供网页文档内容的上下文结构和含义:html本身是没有表现的,我们看到例如<h1>是粗体,字体大小2em,加粗:<strong>是加粗的,不要认为这是html的表现, ...
 - [每日自动更新]Hillstone 山石网科 StoneOS ISP路由表配置文件
			
1.数据基于APNIC,准确有效 2.适用于StoneOS 4.0~5.5各版本 3.对APNIC数据进行路由聚合,实现最小子网 4.覆盖中国大陆地区电信.联通.移动三大运营商,长宽.电信通等二级运营 ...
 - Content Factory:辅助 MonoGame 游戏开发
			
Content Factory 是一款辅助 MonoGame 游戏开发的工具.它提供素材管理的多项功能,包括编译素材.编辑自定义数据等,并能同时应用多个游戏平台. 项目设置 选择要创建游戏项目的平台, ...
 - 诺顿ghost备份恢复系统或分区
			
一 诺顿ghost简介 1,可以克隆分区 也可以克隆磁盘 2,克隆成img或磁盘内容对刻 3,磁盘分区--img---磁盘分区 磁盘---磁盘 二 操作步骤 对刻好的系统 整体思路: 1,A是模板 ...
 - 命令行参数(argc, argv)
			
每个C语言程序都必须有一个称为main()的函数,作为程序启动的起点.当执行程序时,命令行参数(command-line argument)(由shell逐一解析)通过两个入参提供给main()函数. ...