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. selenium读取txt文件的几种方式

    1.用java读取txt文件 public static String readFJ(String path) { path = "D:/workspace/hetong.txt" ...

  2. hihocoder-1389&&2016北京网赛07 Sewage Treatment(二分+网络流)

    题目链接: Sewage Treatment 时间限制:2000ms 单点时限:2000ms 内存限制:256MB 描述 After years of suffering, people could ...

  3. jQuery对象与dom对象的区别与相互转换

    什么是jQuery对象? 就是通过jQuery包装DOM对象后产生的对象.jQuery对象是jQuery独有的,其可以使用jQuery里的方法.例如: $("#test").htm ...

  4. Codeforces Round #275 Div.1 B Interesting Array --线段树

    题意: 构造一个序列,满足m个形如:[l,r,c] 的条件. [l,r,c]表示[l,r]中的元素按位与(&)的和为c. 解法: 线段树维护,sum[rt]表示要满足到现在为止的条件时该子树的 ...

  5. 使用Unity3D引擎开发赛车游戏

    Car Tutorial 在Unity3D的Asset Store有一个赛车的Demo —— Car Tutorial,看起来特别酷的赛车游戏Demo,不过我还没有下载下来,因为在公司下载Assets ...

  6. Colorable Fantasy UI

    Colorable Fantasy UI URL:https://www.assetstore.unity3d.com/#/content/7563 环境要求 Requires Unity 4.0.1 ...

  7. js原生捕鱼达人(三)--完结

    先给分享下我写完的效果,github有点卡,我没有压缩代码,不过效果可以看到 https://jasonwang911.github.io/ 转载请注明'转载于Jason齐齐的博客http://www ...

  8. 在 WinForm 中使用 Direct2D

    在 C# 的 WinForm 应用中,界面的绘制使用的是 GDI+.不过在一些特别的应用中,可能需要用硬件加速来提高绘制的效率.下面就来介绍两种在 WinForm 应用中嵌入 Direct2D 的方法 ...

  9. Google protocol buffer在windows下的编译

    在caffe框架中,使用的数据格式是google的 protocol buffer.对这个不了解,所以,想简单学习一下.简单来说,Protocol Buffer 是一种轻便高效的结构化数据存储格式,可 ...

  10. Ubuntu 14.04 下安装google的浏览器——Chrome

    小编用过好多浏览器,但最后还是选择Chrome, 因为这款浏览器确实做的不错,可是Ubuntu下自带的是火狐,因此小编在这里和大家分享一下如何在Ubuntu下安装chrome浏览器 工具/原料   安 ...