Mapping a collection joined by either of two columns with Fluent NHibernate? - nhibernate

I'm modeling accounting where I have accounts with transactions that debit one account and credit another.
Here are the details of the situation (simplified). My tables (in SQL Server 2008) are:
CREATE TABLE Account
(
AccountID int IDENTITY(1,1) NOT NULL,
AccountNumber nvarchar(10) NOT NULL
)
CREATE TABLE [Transaction]
(
TransactionID [bigint] IDENTITY(1,1) NOT NULL,
DebitAccountID [int] NOT NULL,
CreditAccountID [int] NOT NULL,
Amount money NOT NULL
)
My classes are:
public class Account
{
public int Id { get; set; }
public string AccountNumber { get; set; }
public IList<Transaction> Transactions { get; set; }
}
public class Transaction
{
public int Id { get; set; }
public Account DebitAccount { get; set; }
public Account CreditAccount { get; set; }
}
So the question is "How do I map the Transactions collection in the Account class using fluent NHibernate?"
What I want (for performance reasons) is for the accessing of the transactions collection to execute the query:
SELECT ...
FROM [Transaction]
WHERE DebitAccountID=#accountID OR CreditAccountID=#accountID
The important part there is the OR in the where clause.
So the code I need is:
public class AccountMap : SubclassMap<Account>
{
public AccountMap()
{
Id(x => x.Id).Column("AccountID");
Map(x => x.AccountNumber);
HasMany(x => x.Transactions)
// What goes here to explain this mapping to NHibernate?
.Inverse().Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
Note: I am aware that I could map the transactions as two separate collections, one for "debits" and the other for "credits". That is not an acceptable answer because of performance issues. In particular, there is actually a second type like this related to the account (resulting in even more queries) and mapping as two collections prevents the use of eager loading with Fetch(). The second type is PaymentScheduleLine which contains a plan of all the correct payment transactions over the life of the account. It is associated to the account in the same way as transaction i.e. PaymentScheduleLine has a DebitAccount and CreditAccount and Account has a PaymentSchedule collection. Typically, complex calculations involve the relationship between the transactions and the payment schedule.

I might revise my answer once I hear more about this "second type like this related to the asset," but for now...
Decide what's important and be willing to make sacrifices.
It sounds like performance is your primary concern here, right? You might have to relax your demands that the domain model not be changed or that the query look a certain way.
Batch queries using .Future() to avoid Cartesian products.
You're right that having collections named Debits and Credits could lead to performance problems. Maybe this is the query you're thinking of:
// BAD QUERY - DO NOT USE - cartesian product of rows - Debits X Credits.
var account = session.QueryOver<Account>()
.Fetch(x => x.Debits).Eager
.Fetch(x => x.Credits).Eager
.Where(x => x.Id == accountId)
.SingleOrDefault();
If the account had 1,000 transactions, 500 of them being debits, and 500 credits, then this query would result in 250,000 rows (500 * 500), which is clearly unacceptable!
You don't have to write the query that way, though. This one is better:
var futureAccount = session.QueryOver<Account>()
.Fetch(x => x.Debits).Eager
.Where(x => x.Id == accountId)
.FutureValue();
session.QueryOver<Account>()
.Fetch(x => x.Credits).Eager
.Where(x => x.Id == accountId)
.Future();
var account = futureAccount.Value;
Even though this is really two queries, it will be executed in one round-trip to the database, with only 1,000 rows returned (500 + 500). NHibernate will reuse the same Account instance for the two queries, and just populate whatever data was eagerly fetched. The result will be an Account with fully populated Debits and Credits.
Only fetch the data you need.
Now, 1,000 rows is still a lot. Are you absolutely sure you need to load all of the transactions for a given account? What are you using them for? For the scenario you mentioned, calculating how much money moved from account A to account B over a given timeframe, you would achieve much better performance if you wrote a query to calculate exactly that, or at the very least, only loaded the transactions you were actually interested in using a more specific query. A query like this...
var transactions = session.QueryOver<Transaction>()
.Where(x => x.DebitAccount.Id == fromId
&& x.CreditAccount.Id == toId
&& x.Date >= startDate
&& x.Date < endDate.AddDays(1))
.List();
... could easily cut the number of transactions you're working with from 1,000 to 20 or less.

Related

Can we make Entity Framework create simpler sql?

We're trying to use Entity Framework Code First (5.0) on a legacy database, where we have some problems with a common pattern involving join tables.
For example, a Customer can have multiple Addresses, but the Address table is also used for other types of addresses so there is a join table to link a customer with it's addresses:
Customer -> CustomerAddress -> Address.
The join table CustomerAdress ha a customer-Id and an address-Id.
In our model we only have a Customer class and an Address class, simplified like this:
public class Customer
{
public long Id { get; set; }
public IList<Address> Addresses { get; set; }
}
public class Address
{
public long Id { get; set; }
}
We set up the mapping in OnModelCreating with code like this:
modelBuilder.Entity<Customer>()
.HasMany(o => o.Addresses)
.WithMany()
.Map(m =>
{
m.ToTable("CustomerAddress");
m.MapLeftKey("CustomerId");
m.MapRightKey("AddressId");
});
Everything works fine but when we try to do queries on Customer that Include() Addresses the resulting sql looks very complex with a select from a select with multiple joins, and after adding another relation like this the amount of sql kind of explodes.
If hand-coding the sql for this relationship I would use a left outer join from Customer to CustomerAddress and then a join from CustomerAddress to Address.
Is there any way we could make Entity Framework create a simpler sql-statement for this example?

Table Per Hierarchy - Id unique per sub-class

Given the example at http://notherdev.blogspot.com/2012/01/mapping-by-code-inheritance.html
I have a base class Party and two concrete classes (Company, Person)
I would like to use Table Per Hierachy (Single Table), but my ids are only unique per concrete type.
i.e. Company and Person may have matching Id's
Is there any way to include the discriminator value in the Id as a composite id?
But still be able to call Get<>(id)?
How about this (Fluent):
public class PartyMap : ClassMap<Party>
{
public PartyMap()
{
Table("Parties");
CompositeId<CompositeIdType>(x => x.Id)
.KeyProperty(x => x.IdCompositePart)
.KeyProperty(x => x.Discriminator);
DiscriminateSubClassesOnColumn("Discriminator");
}
}

How to build custom PLINQO query by multiple id's?

Here's my table structure
Places
PlaceId PK
Name
...
PlaceCategories
CatId PK
Name
...
PlaceCats
PlaceId PK
CatId PK
Here's my query that pulls Places based on category id (table join)
public static IQueryable<Places> ByPlaceCat(this Table<Places> table, Expression<Func<PlaceCats, bool>> predicate) {
var db = (DataContext)table.Context;
var innerBizBase = db.PlaceCats.Where(predicate);
return db.Places.Join(innerBizBase, a => a.PlaceId, ab => ab.PlaceId, (a, ab) => a);
}
I use it like this:
places = Db.Places.ByPlaceCat(a => a.CatId == 5);
But I want to be able to pull based on a List<int> of category id's. Looking through the generated PLINQO code, a query that pulls by multiple PlaceId's (but not using a joined table) looks like this:
public static IQueryable<Places> ByPlaceId(this IQueryable<Places> queryable, IEnumerable<long> values)
{
return queryable.Where(p => values.Contains(p.PlaceId));
}
How could I essentially merge those two queries, to let me pass in a List<int> of CatId's to query by? This LINQ/PLINQO query is melting my brain. Thanks in advance!
You would need to write a extension method like this:
public static IQueryable<Places> ByPlaceCats(this Table<Places> table, IEnumerable<int> catIds)
{
var db = (TestDataContext)table.Context;
var places = (from placeCat in db.PlaceCats
join place in db.Places on placeCat.PlaceId equals place.PlaceId
where catIds.Contains(placeCat.CatId)
select place);
return places;
}
Please note that the PlaceCats table could be made into a ManyToMany relationship by adding two foreign keys to the proper tables. Once this change has been made than PLINQO will automatically generate the correct code and will create a link between the two tables skipping the intermediary table. So you could get a collection of PlaceCategories associated to the current Places entity by accessing a property on the Places entity.
Please remember to contact us if you have any questions and be sure to check out the community forums located here and PLINQO forums here.
Thanks
-Blake Niemyjski (CodeSmith Support)

Retrieve fewer table fields using Linq To Sql lambda expressions

I'd like to know how to get a subset of data from a table using Linq To SQl Lambda expressions...
Let's say I have a table tbl_Product, with fields ProductId, ProductCode, ProductName, Description, WhyBuyCopy, how do I get only the first three fields when the others are not needed given that retrieving all data takes a second longer (checked using 'set statistics time on')?
One solution could be to create a partial class that extends that created by linq2sql, with only the fields needed, but I am trying to avoid that...
The other solution of course is to use
from p in base.dc.E_Products
select new E_Product
{
ProductId = p.ProductId,
ProductCode = p.ProductCode,
etc
})
but I am very curious to know whether there is an equivalent lambda expression for the above code.
Thank you
The second solution you propose does not work with LINQ to SQL, because it won't allow you to create a new LINQ to SQL entity within a LINQ query, since this entity won't have change tracking.
Your best option is to use an anonymous type (if possible) or create a Data Transfer Object (DTO) with only those two fields:
public class ProductDto
{
public ProductId { get; set; }
public ProductCode { get; set; }
}
from p in base.dc.E_Products
select new ProductDto()
{
ProductId = p.ProductId,
ProductCode = p.ProductCode,
});

How to query for most commonly used many-to-one in nhibernate

I have the following issue in the project I am working on. Each transaction in the system is assigned to a given user. So there is a many to one relationship between transactions and users, like so:
public class User
{
public int ID { get; private set; }
public string FirstName { get; set; }
....
}
public class Transaction
{
public int ID { get; private set; }
public User CreatedBy { get; private set; }
...
}
I have mapped these entities with NHibernate so that there is a many-to-one mapping between the Transaction and the User classes. The User object doesn't have a list of transactions, but the Transaction has a reference to the User that created it.
Now I want to query to retrieve a list of the users who created the most transactions, but I can't figure out how to get the top 10 most referenced users using NHibernate.
Any ideas? I would like to be able to use ICriteria to complete this rather than HQL, but HQL would be ok if required.
Update
I tried sirrocco's suggestion with the query as...
DetachedCriteria topReferencedUsers = DetatchedCriteria.For(typeof(Transaction))
.SetProjection(Projections.GroupProperty("CreatedBy.Id"))
.SetProjection(Projections.Count("CreatedBy.Id").As("pcount" ))
.AddOrder(Order.Desc("pcount"))
.SetMaxResults(10);
and build that as the subquery...
GetSession().CreateCriteria(typeof (User))
.Add(Subqueries.PropertyIn("Id", topReferencedUsers))
.List<User>();
but this subquery does not group but returns the total number of transactions, which are then used as the IN clause to the User query. If I add the ProjectionList() with both projections, I get the output of the subquery that I want, but it fails because it tries to run the two column output into the IN clause of the User query. How do I get NHibernate to project both the ID and the Count, but only join on the ID?
Update (2)
I tried Sirrocco's SqlGroupProjection suggestion (thank you Sirrocco) but came up empty. First it gave me errors saying that it couldn't find the property pcount, which meant that I needed to remove the order by, which means it was ordering by some timestamp, which won't work. But even with that, it is still only outputing the count of the times that the user was referenced, not the user id with which to join the Users table. Any ideas? Thanks.
You can try it for yourself and see if you get the desired output.
var userIds = this.Session
.CreateQuery(#"
select a.User.Id
from Transaction as a
group by a.User
order by count(a.User) desc")
.SetMaxResults(10)
.List<int>().ToArray();
var users = this.Session.CreateCriteria(typeof(User))
.Add(Restrictions.InG("Id", userIds))
.List<Artist>();
return users;
The userId's that I get from the first queries are (90,22,50,55) but when passed to the second one I get my users in 22,50,55,90 order.
You could split the operation into two steps.
1) Execute topReferencedUsers, then extract the CreatedBy.Id projection into an int array in memory (since you're only dealing with 10).
2) Then Execute:
GetSession().CreateCriteria(typeof(User))
.Add(Expression.InG<int>("Id", topTenIdArray))
.List<User>();