NHibernate - 父表中的 KeyColumn

发布于 2024-09-19 08:30:36 字数 917 浏览 2 评论 0原文

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

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

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

ProductTransactions:
- TransactionID (PK, FK)
- Discount

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

我使用 Fluent NHibernate 进行了映射,以便 ProductTransaction 继承自 Transaction 并使用 SubclassMap。我对 ProductTransactionDetail 和 TransactionDetail 做了同样的事情。我还有一个名为“Details”的属性,它是我的 Transaction 实体上的 TransactionDetail 列表,具有以下映射:

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

我希望能够在我的 ProductTransaction 实体上覆盖它。当使用 virtual 并覆盖时,编译器会抱怨,但新的 virtual 似乎可以工作。我遇到的问题是如何映射它,因为 ProductTransactionDetails 表中没有 TransactionID 列。它需要以某种方式从父表中获取它,但我不知道如何做到这一点。

如果有人可以帮助解决我遇到的问题,或者让我知道我是否以错误的方式处理事情,我将不胜感激。

谢谢

my application has the following database structure:

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

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

ProductTransactions:
- TransactionID (PK, FK)
- Discount

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

I have this mapped using Fluent NHibernate so that ProductTransaction inherits from Transaction and uses a SubclassMap. I did the same for ProductTransactionDetail and TransactionDetail. I also have a property called "Details" which is a list of TransactionDetail on my Transaction entity with the following mapping:

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

I'd like to be able to override this on my ProductTransaction entity. When using virtual and override the compiler complained but new virtual seemed to work. The problem i have is how i map this since the ProductTransactionDetails doesn't have the TransactionID column in the table. It needs to somehow grab it from the parent table but i'm not sure how to do this.

I'd appreciate it if someone could help fix the issue i'm having or let me know if i'm going about things in the wrong way.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

和我恋爱吧 2024-09-26 08:30:36

注释在代码中...

领域模型

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()
            );
    }
}

Comments are in the code...

Domain Model

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 Mapping

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()
            ;
    }
}

Unit Tests

[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()
            );
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文