使用NHibernate实现存储库

时间:2023-03-09 03:27:27
使用NHibernate实现存储库

ORM框架不是存储库。存储库是一种架构模式,而ORM是将数据模型表示成对象模型的一种手段。

存储库可以使用ORM来协助充当领域模型和数据模型之间的中介。

NHibernate允许在不损坏或只少量损坏领域模型的情况下直接将领域模型映射到数据模型。

下面就以一个在线拍卖的例子来说明。

自动竞价逻辑:

  1. 当一个竞标者出价时,他会输入他愿意为该物品支付的最大金额。不过,他的实际出价会是超过上一次出价或起拍价所需的最小金额。
  2. 当第二个竞标者出价时,一个自动竞标器会代表前一个竞标者出价,直到达到其最大金额或足以击败第二个竞标者时才停止。
  3. 如果第二个竞标者超过了前一个竞标者的最大出价,则会通知前一个竞标者他的出价已经被超过了。
  4. 如果第二个竞标者的出价与前一个竞标者的最大出价相同,则前一个竞标者仍旧是本次拍卖的赢家,因为他先出价。

实体基类:每个实体需要提供一个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文件需要在生成操作更改成嵌入的资源

使用NHibernate实现存储库

出价历史存储库实现:提供保存和查询数量功能

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实现存储库

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

使用NHibernate实现存储库

使用NHibernate实现存储库

如果NHibernate配置如下:

<property name="show_sql">true</property>

会输出SQL:

使用NHibernate实现存储库

整体的结构如图:

使用NHibernate实现存储库

使用NHibernate实现存储库

使用NHibernate实现存储库

上面是领域层类库,下面是表现层控制台应用程序。

领域层分为模型、基础设施、应用程序服务。

实体、值对象、存储库接口、领域错误定义在模型层。

用例、查询定义在应用程序服务中。

存储库实现、领域事件基类、实体基类、值对象基类、ORM配置项等放在基础设施层中。

相关文章