代码之家  ›  专栏  ›  技术社区  ›  nfplee

nHibernate-父表中的键列

  •  2
  • nfplee  · 技术社区  · 14 年前

    我的应用程序具有以下数据库结构:

    Transactions:
    - TransactionID (PK, Identity)
    - Type
    - TotalAmount
    
    TransactionDetails:
    - TransactionDetailID (PK, Identity)
    - TransactionID (PK)
    - Amount
    
    ProductTransactions:
    - TransactionID (PK, FK)
    - Discount
    
    ProductTransactionDetails:
    - TransactionDetailID (PK, FK)
    - ProductID (FK)
    

    我使用FluentNHibernate进行了映射,以便productTransaction继承事务并使用子类映射。我对ProductTransactionDetail和TransactionDetail也做了同样的操作。我还有一个名为“details”的属性,它是我的事务实体上的TransactionDetail列表,具有以下映射:

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

    我希望能够在我的ProductTransaction实体上重写此项。当使用virtual和override时,编译器抱怨,但新的virtual似乎可以工作。我的问题是如何映射它,因为productTransactionDetails表中没有TransactionId列。它需要以某种方式从父表中获取它,但我不确定如何执行此操作。

    如果有人能帮我解决我遇到的问题,我会很感激的,如果我做错事的话,我会告诉我的。

    谢谢

    1 回复  |  直到 14 年前
        1
  •  0
  •   Faron    14 年前

    注释在代码中…

    域模型

    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
    }
    

    Fluent映射

    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()
                );
        }
    }
    
    推荐文章