2010-09-17 44 views
2

我的應用有以下數據庫結構:NHibernate的 - KeyColumn從父表

Transactions: 
- TransactionID (PK, Identity) 
- Type 
- TotalAmount 

TransactionDetails: 
- TransactionDetailID (PK, Identity) 
- TransactionID (PK) 
- Amount 

ProductTransactions: 
- TransactionID (PK, FK) 
- Discount 

ProductTransactionDetails: 
- TransactionDetailID (PK, FK) 
- ProductID (FK) 

我有這個映射使用功能NHibernate使ProductTransaction從交易繼承和使用SubclassMap。我爲ProductTransactionDetail和TransactionDetail做了同樣的事情。我也有一個所謂的「詳細信息」屬性,它是與以下映射在我的交易實體TransactionDetail的列表:

HasMany(x => x.Details) 
    .KeyColumn("TransactionID") 
    .Inverse() 
    .Cascade.All(); 

我希望能夠覆蓋這對我ProductTransaction實體。當使用虛擬並覆蓋編譯器抱怨,但新的虛擬似乎工作。我遇到的問題是我如何映射這個,因爲ProductTransactionDetails沒有表中的TransactionID列。它需要以某種方式從父表中獲取它,但我不知道如何做到這一點。

如果有人能幫助解決我遇到的問題,或者讓我知道我是否以錯誤的方式處理事情,我將不勝感激。

感謝

+0

另一個問題是相反的情況。假設我有以下session.Linq ()。其中​​(d => d.Transaction.Discount> 0)。屬性事務屬於事務類型,而不是ProductTransaction,所以我無法訪問Discount屬性。我希望這是一個相當普遍的問題,但我猜不是:(. – nfplee 2010-09-20 11:50:15

+0

爲什麼不在ProductTransactionDetail上有一個只讀屬性,它返回Transaction轉換爲ProductTransaction?這可以在你的linq例子中工作。 – DanP 2010-09-21 10:20:02

+0

歡迎您的回覆。嘗試做:public virtual ProductTransaction ProductTransaction {get {return(ProductTransaction)Transaction;}}但它拋出了錯誤:「無法將類型爲'TransactionProxyb340b6c ...'的對象類型爲'ProductTransaction'。」。 – nfplee 2010-09-22 07:56:33

回答

0

評論是在代碼...

域模型

public class Product : IEquatable<Product> 
{ 
    protected internal virtual int Id { get; set; } 

    public virtual bool Equals(Product other) 
    { 
     if (ReferenceEquals(null, other)) return false; 
     if (ReferenceEquals(this, other)) return true; 
     return other.Id == Id; 
    } 

    #region Implementation of IEquatable 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     if (obj.GetType() != typeof (Product)) return false; 
     return Equals((Product) obj); 
    } 

    public override int GetHashCode() 
    { 
     return Id; 
    } 

    public static bool operator ==(Product left, Product right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(Product left, Product right) 
    { 
     return !Equals(left, right); 
    } 

    #endregion Implementation of IEquatable 
} 

public class Transaction : IEquatable<Transaction> 
{ 
    private IList<TransactionDetail> details; 

    // This is declared protected because it is an implementation 
    // detail that does not belong in the public interface of the 
    // domain model. It is declared internal so the fluent mapping 
    // can see it. 
    protected internal virtual int Id { get; set; } 

    public virtual double TotalAmount { get; set; } 

    // This is declared as a IList even though it is recommended 
    // to use ICollection for a Bag because the the Testing Framework 
    // passes a HashSet to NHibernate and NHibernate attempts to cast 
    // it to a List since it is declared a Bag in the mapping. 
    public virtual IList<TransactionDetail> Details 
    { 
     // I lazily initialize the collection because I do not like 
     // testing for nulls all through my code but you may see 
     // issues with this if you use Cascade.AllDeleteOrphan in 
     // the mapping. 
     get { return details ?? (details = new List<TransactionDetail>()); } 
     set { details = value; } 
    } 

    #region Implementation of IEquatable 

    // Do not forget to declare this function as virtual or you will 
    // get a mapping exception saying that this class is not suitable 
    // for proxying. 
    public virtual bool Equals(Transaction other) 
    { 
     if (ReferenceEquals(null, other)) return false; 
     if (ReferenceEquals(this, other)) return true; 
     return other.Id == Id; 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     if (obj.GetType() != typeof(Transaction)) return false; 
     return Equals((Transaction)obj); 
    } 

    public override int GetHashCode() 
    { 
     return Id; 
    } 

    public static bool operator ==(Transaction left, Transaction right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(Transaction left, Transaction right) 
    { 
     return !Equals(left, right); 
    } 

    #endregion Implementation of IEquatable 
} 

public class TransactionDetail : IEquatable<TransactionDetail> 
{ 
    protected internal virtual int Id { get; set; } 
    public virtual double Amount { get; set; } 

    #region Implementation of IEquatable 

    public virtual bool Equals(TransactionDetail other) 
    { 
     if (ReferenceEquals(null, other)) return false; 
     if (ReferenceEquals(this, other)) return true; 
     return other.Id == Id; 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     if (obj.GetType() != typeof (TransactionDetail)) return false; 
     return Equals((TransactionDetail) obj); 
    } 

    public override int GetHashCode() 
    { 
     return Id; 
    } 

    public static bool operator ==(TransactionDetail left, TransactionDetail right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(TransactionDetail left, TransactionDetail right) 
    { 
     return !Equals(left, right); 
    } 

    #endregion Implementation of IEquatable 
} 

public class ProductTransaction : Transaction, IEquatable<ProductTransaction> 
{ 
    public virtual double Discount { get; set; } 

    // This is declared 'new' because C# does not support covariant 
    // return types until v4.0. This requires clients to explicitly 
    // cast objects of type Transaction to ProductTransaction before 
    // invoking Details. Another approach would be to change the 
    // property's name (e.g., ProductDetails) but this also requires 
    // casting. 
    public virtual new IList<ProductTransactionDetail> Details 
    { 
     get { return base.Details.OfType<ProductTransactionDetail>().ToList(); } 
     set { base.Details = null == value ? null : value.Cast<TransactionDetail>().ToList(); } 
    } 

    #region Implementation of IEquatable 

    public virtual bool Equals(ProductTransaction other) 
    { 
     return base.Equals(other); 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     return Equals(obj as ProductTransaction); 
    } 

    public override int GetHashCode() 
    { 
     return base.GetHashCode(); 
    } 

    public static bool operator ==(ProductTransaction left, ProductTransaction right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(ProductTransaction left, ProductTransaction right) 
    { 
     return !Equals(left, right); 
    } 

    #endregion Implementation of IEquatable 
} 

public class ProductTransactionDetail : TransactionDetail, IEquatable<ProductTransactionDetail> 
{ 
    public virtual Product Product { get; set; } 

    #region Implementation of IEquatable 

    public virtual bool Equals(ProductTransactionDetail other) 
    { 
     return base.Equals(other); 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     return Equals(obj as ProductTransactionDetail); 
    } 

    public override int GetHashCode() 
    { 
     return base.GetHashCode(); 
    } 

    public static bool operator ==(ProductTransactionDetail left, ProductTransactionDetail right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(ProductTransactionDetail left, ProductTransactionDetail right) 
    { 
     return !Equals(left, right); 
    } 

    #endregion Implementation of IEquatable 
} 

流利的映射

internal sealed class ProductMap : ClassMap<Product> 
{ 
    internal ProductMap() 
    { 
     Table("Product") 
      ; 
     LazyLoad() 
      ; 
     Id(x => x.Id) 
      .Column("ProductId") 
      .GeneratedBy.Identity() 
      ; 
    } 
} 

internal sealed class TransactionMap : ClassMap<Transaction> 
{ 
    internal TransactionMap() 
    { 
     // The table name is surrounded by back ticks because 
     // 'Transaction' is a reserved word in SQL. On SQL Server, 
     // this translates to [Transaction]. 
     Table("`Transaction`") 
      ; 
     LazyLoad() 
      ; 
     Id(x => x.Id) 
      .Column("TransactionId") 
      .GeneratedBy.Identity() 
      ; 
     Map(x => x.TotalAmount) 
      .Column("TotalAmount") 
      .Not.Nullable() 
      ; 
     // You should consider treating TransactionDetail as a value 
     // type that cannot exist outside a Transaction. In this case, 
     // you should mark the relation as Not.Inverse and save or 
     // update the transaction after adding a detail instead of 
     // saving the detail independently. 
     HasMany(x => x.Details) 
      .KeyColumn("TransactionID") 
      .Cascade.All() 
      .Not.Inverse() 
      .AsBag() 
      ; 
     // You have a Type column in your example, which I took to 
     // mean that you wanted to use the Table Per Hierarchy 
     // strategy. It this case you need to inform NHibernate 
     // which column identifies the subtype. 
     DiscriminateSubClassesOnColumn("Type") 
      .Not.Nullable() 
      ; 
    } 
} 

internal sealed class TransactionDetailMap : ClassMap<TransactionDetail> 
{ 
    internal TransactionDetailMap() 
    { 
     Table("TransactionDetail") 
      ; 
     LazyLoad() 
      ; 
     Id(x => x.Id) 
      .Column("TransactionDetailId") 
      .GeneratedBy.Identity() 
      ; 
     Map(x => x.Amount) 
      .Column("Amount") 
      .Not.Nullable() 
      ; 
    } 
} 

internal sealed class ProductTransactionMap : SubclassMap<ProductTransaction> 
{ 
    internal ProductTransactionMap() 
    { 
     KeyColumn("TransactionId") 
      ; 
     // I recommend giving the discriminator column an explicit 
     // value for a subclass. Otherwise, NHibernate uses the fully 
     // qualified name of the class including the namespace. If 
     // you later move the class to another namespace or rename 
     // the class then you will have to migrate all of the data 
     // in your database. 
     DiscriminatorValue("TransactionKind#product") 
      ; 
     Map(x => x.Discount) 
      .Column("Discount") 
      .Nullable() 
      ; 
     // Do not map the over-ridden version of 
     // the Details property. It is handled 
     // by the base class mapping. 
    } 
} 

internal sealed class ProductTransactionDetailMap : SubclassMap<ProductTransactionDetail> 
{ 
    internal ProductTransactionDetailMap() 
    { 
     // There was no Type column in your example for this table, 
     // whcih I took to mean that you wished to use a Table Per 
     // Class strategy. In this case, you need to provide the 
     // table name even though it is a subclass. 
     Table("ProductTransactionDetail") 
      ; 
     KeyColumn("TransactionDetailId") 
      ; 
     References(x => x.Product) 
      .Column("ProductId") 
      .Not.Nullable() 
      ; 
    } 
} 

單元測試

[TestClass] 
public class UnitTest1 
{ 
    private static ISessionFactory sessionFactory; 
    private static Configuration configuration; 

    [TestMethod] 
    public void CanCorrectlyMapTransaction() 
    { 
     using (var dbsession = OpenDBSession()) 
     { 
      var product = new Product(); 
      dbsession.Save(product); 

      new PersistenceSpecification<Transaction>(dbsession) 
       .CheckProperty(t => t.TotalAmount, 100.0) 
       .CheckComponentList(
        t => t.Details, 
        new[] { 
         new TransactionDetail { 
          Amount = 75.0, 
         }, 
         new ProductTransactionDetail { 
          Amount = 25.0, 
          Product = product, 
         }, 
        } 
       ) 
       .VerifyTheMappings() 
       ; 
     } 
    } 

    private static Configuration Configuration 
    { 
     get 
     { 
      return configuration ?? (
       configuration = forSQLite().Mappings(
        m => m.FluentMappings 
         .Conventions.Setup(x => x.Add(AutoImport.Never())) 
         .Add(typeof(ProductMap)) 
         .Add(typeof(ProductTransactionMap)) 
         .Add(typeof(ProductTransactionDetailMap)) 
         .Add(typeof(TransactionMap)) 
         .Add(typeof(TransactionDetailMap)) 
       ) 
       .BuildConfiguration() 
      ); 
     } 
    } 

    private static ISessionFactory SessionFactory 
    { 
     get { return sessionFactory ?? (sessionFactory = Configuration.BuildSessionFactory()); } 
    } 

    private static ISession OpenDBSession() 
    { 
     var session = SessionFactory.OpenSession(); 

     // Ideally, this would be done once on the database 
     // session but that does not work when using SQLite as 
     // an in-memory database. It works in all other cases. 
     new SchemaExport(configuration) 
      .Execute(
       true,     // echo schema to Console 
       true,     // create schema on connection 
       false,    // just drop do not create 
       session.Connection, // an active database connection 
       null     // writer for capturing schema 
      ); 

     return session; 
    } 

    private static FluentConfiguration forSQLite() 
    { 
     return Fluently.Configure() 
      .Database(
       SQLiteConfiguration 
        .Standard 
        .InMemory() 
        .ShowSql() 
      ); 
    } 
} 
+0

嗨,謝謝你我試着將Details屬性添加到我的ProductTransaction實體中,但它仍然會拋出錯誤「Invalid Cast(檢查您的映射是否屬性類型不匹配); ProductTransaction的setter」,我還需要做哪些其他更改? – nfplee 2010-09-27 09:33:43