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文件需要在生成操作更改成嵌入的资源

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

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实现存储库的更多相关文章

  1. [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 ...

  2. 用邻接表或vector实现存边以及具体如何调用[模板]

    存边: 对于指针实现的邻接表: struct edge{ int from,next,to,w; }E[maxn]; int head[maxn],tot=0;//head初始化为-1: void a ...

  3. vector(实现存图)

    #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #i ...

  4. java TreeSet 实现存自定义不可重复数据

    本文主要是介绍一下java集合中的比较重要的Set接口下的可实现类TreeSet TreeSet类,底层用二叉树的数据结构 * 集合中以有序的方式插入和抽取元素. * 添加到TreeSet中的元素必须 ...

  5. java+mybatis实现一个简单的银行系统,实现存取款与账户查询

    先创建数据库和表,使用的是MySQL数据库. create database mybatis; use mybatis; CREATE TABLE `accountdo` ( `id` varchar ...

  6. 结合实体框架(代码优先)、工作单元测试、Web API、ASP. net等,以存储库设计模式开发示例项目。NET MVC 5和引导

    介绍 这篇文章将帮助你理解在库模式.实体框架.Web API.SQL Server 2012.ASP中的工作单元测试的帮助下设计一个项目.净MVC应用程序.我们正在开发一个图书实体和作者专用的样例图书 ...

  7. ABP框架学习

    一.总体与公共结构 1,ABP配置 2,多租户 3,ABP Session 4,缓存 5,日志 6,设置管理 7,Timing 8,ABPMapper 9,发送电子邮件 二.领域层 10,实体 11, ...

  8. abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

    Introduction With AspNet MVC Web API EntityFramework and AngularJS 地址:https://aspnetboilerplate.com/ ...

  9. [半翻] 设计面向DDD的微服务

    这篇文章行文结构对照微软博客, 结合本人意译和多年实践的回顾思考形成此次读书笔记. Domian-driven Design 领域-驱动-设计(DDD)提倡基于(用例相关的现实业务)进行建模. 1. ...

随机推荐

  1. java截取日期范围并计算相差月数

    前两天,媳妇单位让整理excel的某一个单元格内两个日期范围的相差月数,本人对excel操作不是很熟练,便写了个小程序计算了一下,原始需求如下: 计算投资期限的范围,并得到期限范围的相差月数 思路1: ...

  2. 【MVC 4】6.SportsSore:导航

     作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> 前面的文章[MVC 4]5.SportsSore —— 一个真实的应用程序 建立了 Sports ...

  3. hdu-5929 Basic Data Structure(双端队列+模拟)

    题目链接: Basic Data Structure Time Limit: 7000/3500 MS (Java/Others)    Memory Limit: 65536/65536 K (Ja ...

  4. 利用WinPcap模拟网络包伪造飞秋闪屏报文

    起因 不知道从什么时候开始,同事开始在飞秋上发闪屏振动了,后来变本加厉,成了每日一闪.老闪回去也比较麻烦,作为程序猿呢,有没有什么偷懒的办法呢?(同事负责用户体验,不大懂编程).然后尝试了以下思路: ...

  5. Mecanim的Avater

    角色共用同一套动作原理 先说说为什么不同的角色可以共用同一套动作:因为导入之后,我们需要为它们每一个模型都创建一个Avater,而Avater里存储了骨骼的蒙皮信息(创建Avater时把三维软件里的蒙 ...

  6. 【转】WCF与Web API 区别(应用场景)

    Web api  主要功能: 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作 请求的回 ...

  7. 构建基于WCF Restful Service的服务

    前言 传统的Asmx服务,由于遵循SOAP协议,所以返回内容以xml方式组织.并且客户端需要添加服务端引用才能使用(虽然看到网络上已经提供了这方面的Dynamic Proxy,但是没有这种方式简便), ...

  8. Ace 动画应用实例 ------启动欢迎界面

    仔细观察手机应用很多都用到了动画效果 那么咱们也做一个 除了位移没有使用 其他都有 布局随便放张图片 public class SplashActivity extends Activity { pr ...

  9. Android连接网络打印机,jSocket连接网络打印机

    老大写的一个打印工具类,记录一下. package com.Ieasy.Tool; import android.annotation.SuppressLint; import java.io.IOE ...

  10. Linux操作系统基础(完结)

    摘要 一.Linux操作系统概述 二.Linux操作系统安装 三.Linux文件系统及文件基础 四.Linux操作系统命令使用基础 五.Linux应用程序的安装与卸载基础 五.用户及进程 六.相关信息 ...