Optimizing EF flattening - sql

I have a similar case to the following:
Say there's a number of jobs to be done and for each job there's a history of workers where only one worker is active per job. There's three tables: the Job itself, a mapping table JobWorkers which holds the history of workers for a given job (including a datetime "To" which indicates whether still active (null) or when assignment was cancelled (end date)) and Workers which have a first and last name.
I'd like to query a list of all jobs and the first and last name of the currently assigned worker as flat model. This is the code I'm executing:
var jobExample = dbContext.Jobs.Select(j => new
{
j.JobId,
// ...some other columns from jobs table
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.FirstName, // first name of currently assigned worker
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.LastName // last name of currently assigned worker
}).First();
The following SQL query is generated:
SELECT TOP (1)
[Extent1].[JobId] AS [JobId],
[Extent3].[FirstName] AS [FirstName],
[Extent5].[LastName] AS [LastName]
FROM [tables].[Jobs] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent2]
WHERE ([Extent1].[JobId] = [Extent2].[JobId]) AND ([Extent2].[To] IS NULL) ) AS [Limit1]
LEFT OUTER JOIN [tables].[Workers] AS [Extent3] ON [Limit1].[WorkerId] = [Extent3].[WorkerId]
OUTER APPLY (SELECT TOP (1)
[Extent4].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent4]
WHERE ([Extent1].[JobId] = [Extent4].[JobId]) AND ([Extent4].[To] IS NULL) ) AS [Limit2]
LEFT OUTER JOIN [tables].[Workers] AS [Extent5] ON [Limit2].[WorkerId] = [Extent5].[WorkerId]
As one can see there're two outer apply/left outer joins that are identical. I'd like to get rid of one of those to make the query more performant.
Note that the select statement is dynamically generated based on what information the user actually wants to query. But even if this didn't apply I'm not sure how to do this without having a hierarchic structure and then only afterwards flatten it in .NET
Thanks for your help and if I can improve this question in any way please comment.

You've probably seen that there are two types of LINQ methods: the ones that return IQueryable<...>, and the other ones.
Methods of the first group use deferred execution. This means, that the query is made, but not executed yet. Your database is not contacted.
Methods of the second group, like ToList(), FirstOrDefault(), Count(), Any(), will execute the query: they will contact the database, and fetch the data that is needed to calculate the result.
This is the reason, that you should try to postpone any method of the second group to as last as possible. If you do it earlier, and you do something LINQy after it, changes are that you fetch to much data, or, as in your case: that you do execute the same code twice.
The solution is: move your FirstOrDefault to a later moment.
var jobExample = dbContext.Jobs.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(worker => new
{
FirstName = worker.FirstName,
LastName = worker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();
The result is slightly different than yours:
Id = 10;
... // other Job properties
// the current active worker:
ActiveWorker =
{
FirstName = "John",
LastName = "Doe",
}
If you really want an object with Id / FirstName / LastName, add an extra Select before your final FirstOrDefault:
.Select(jobWithActiveWorker => new
{
Id = jobWithActiveWorker.Id,
... // other Job properties
// properties of the current active worker
FirstName = jobWithActiveWorker.FirstName,
LastName = jobWithActiveWorker.LastName,
})
.FirstOrDefault();
Personally I think that you should not mix Job properties with Worker properties, so I think the first solution: "Job with its currently active worker" is neater: the Job properties are separated from the Worker properties. You can see why that is important if you also wanted the Id of the active worker:
.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(jobworker => new
{
Id = jobworker.Id,
FirstName = jobworker.FirstName,
LastName = jobworker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();

Try rewriting your query like this:
var query =
from j in dbContext.Jobs
let ws = j.JobWorker
.Where(jw => jw.To == null)
.Select(jw => jw.Worker)
.Take(1)
from w in ws.DefaultIfEmpty()
select new
{
j.JobId,
// other properties
w.FirstName,
w.LastName,
};
The query processor probably could not have optimized any further to know it could use the subquery once.

Related

How can I optimize slow (not-so) complex queries in Entity Framework Core 2.1

I have a LINQ query that makes string search within a few tables. The query however is painfully slow on big tables. At my first attempt, I was getting a timeout. I was able to improve the performance a little. This is the first version of the code:
public ListResponse<UserDTO> GetUsers(FilterParameters filter)
{
var query = from user in _dbContext.Users
.Include(w => w.UserRoles).ThenInclude(u => u.Role)
join accountHolder in _dbContext.AccountHolders
.Include(c => c.OperationCountry)
.Include(x => x.Accounts)
.ThenInclude(x => x.Currency)
on user.Id equals accountHolder.ObjectId into aHolder
from a in aHolder.DefaultIfEmpty()
select new UserDTO
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Username = user.UserName,
Email = user.Email,
Roles = Mapper.Map<IList<RoleDTO>>(user.UserRoles.Select(i => i.Role)),
LastActivity = user.LastActivity,
CreatedAt = user.CreatedAt,
EmailConfirmed = user.EmailConfirmed,
AccountBalance = a.Accounts.Where(p => p.CurrencyId == a.OperationCountry.LocalCurrencyId).Single().Balance,
AccountReference = a.Accounts.Where(p => p.CurrencyId == a.OperationCountry.LocalCurrencyId).Single().AccountRef
};
// Apply search term
if (!IsNullOrEmpty(filter.SearchTerm))
query = query.Where(w =>
w.FirstName.Contains(filter.SearchTerm)
w.LastName.Contains(filter.SearchTerm) ||
w.Email.Contains(filter.SearchTerm) ||
w.AccountReference.Contains(filter.SearchTerm));
if (filter.ColumnFilters != null)
{
if (filter.ColumnFilters.ContainsKey("EmailConfirmed"))
{
var valueStr = filter.ColumnFilters["EmailConfirmed"];
if (bool.TryParse(valueStr, out var value))
query = query.Where(x => x.EmailConfirmed == value);
}
}
// Get total item count before pagination
var totalItemCount = query.Count();
// Apply pagination
query = query.ApplySortAndPagination(filter);
var userDtoList = query.ToList();
return new ListResponse<UserDTO>()
{
List = userDtoList,
TotalCount = totalItemCount
};
}
I suspected non-database code in the query (such as Single, and Mapping) was causing a slow query so I made an effort to get rid of them. I am still curious how to get a single Account without calling Single() inside the query. Here's the modified version.
public ListResponse<UserDTO> GetUsers(FilterParameters filter)
{
var query = from user in _dbContext.Users
.Include(w => w.UserRoles)
.ThenInclude(u => u.Role)
.Include(w => w.AccountHolder)
.ThenInclude(c => c.OperationCountry)
.Include(w => w.AccountHolder)
.ThenInclude(c => c.Accounts)
.ThenInclude(x => x.Currency)
select user;
if (!IsNullOrEmpty(filter.SearchTerm))
{
query = query.Where(w =>
w.FirstName.StartsWith(filter.SearchTerm) ||
w.LastName.StartsWith(filter.SearchTerm) ||
w.UserName.StartsWith(filter.SearchTerm) ||
w.AccountHolder.Accounts.Any(x => x.AccountRef.StartsWith(filter.SearchTerm)));
}
// total before pagination
var totalItemCount = query.Count();
// Nothing fancy, just OrderBy(filter.OrderBy).Skip(filter.Page).Take(filter.Length)
query = query.ApplySortAndPagination(filter);
userList = query.ToList() //To deal with "Single" calls below, this returns at most filter.Length records
var userDtoResult = (from user in query
select new UserDTO
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Username = user.UserName,
Email = user.Email,
Roles = Mapper.Map<IList<RoleDTO>>(user.UserRoles.Select(i => i.Role)),
LastActivity = user.LastActivity,
CreatedAt = user.CreatedAt,
EmailConfirmed = user.EmailConfirmed,
AccountBalance = user.AccountHolder.Accounts.Single(p => p.CurrencyId == user.AccountHolder.OperationCountry.LocalCurrencyId).Balance
AccountReference = user.AccountHolder.Accounts.Single(p => p.CurrencyId == user.AccountHolder.OperationCountry.LocalCurrencyId).AccountRef
}).ToList();
return new ListResponse<UserDTO>()
{
List = userDtoResult,
TotalCount = totalItemCount
};
}
The SQL query generated by this query runs slow too, whereas if I write a join query in SQL, it completes in a few hundred milliseconds. I am suspecting I am suffering from N+1 Query problem, but not sure since EF seems to generate a single query when I trace in the SQL Server Profiler.
This is the query generated by the Entity framework and runs in about 8 seconds when I run on the SSMS:
exec sp_executesql N'SELECT TOP(#__p_4) [w].[Id], [w].[AccessFailedCount], [w].[ConcurrencyStamp], [w].[CreatedAt], [w].[CreatedBy], [w].[DeletedAt], [w].[DeletedBy], [w].[DetailId], [w].[Email], [w].[EmailConfirmed], [w].[EmailConfirmedAt], [w].[FacebookId], [w].[FirstName], [w].[GoogleId], [w].[IsActive], [w].[IsDeleted], [w].[LastActivity], [w].[LastName], [w].[LockoutEnabled], [w].[LockoutEnd], [w].[NormalizedEmail], [w].[NormalizedUserName], [w].[Password], [w].[PasswordHash], [w].[PhoneNumber], [w].[PhoneNumberConfirmed], [w].[RoleId], [w].[SecurityStamp], [w].[TwoFactorEnabled], [w].[UpdatedAt], [w].[UpdatedBy], [w].[UserName], [w].[WorkflowId], [t].[Id], [t].[AccountHolderLevel], [t].[AccountHolderType], [t].[CreatedAt], [t].[CreatedBy], [t].[DeletedAt], [t].[DeletedBy], [t].[IsDeleted], [t].[ObjectId], [t].[OperationCountryId], [t].[UpdatedAt], [t].[UpdatedBy], [t0].[Id], [t0].[ContinentId], [t0].[CountryCode], [t0].[CreatedAt], [t0].[CreatedBy], [t0].[DeletedAt], [t0].[DeletedBy], [t0].[ISOCode2], [t0].[IsActive], [t0].[IsDeleted], [t0].[IsOperational], [t0].[LocalCurrencyId], [t0].[Name], [t0].[PhoneCode], [t0].[PostCodeProvider], [t0].[Regex], [t0].[SmsProvider], [t0].[UpdatedAt], [t0].[UpdatedBy]
FROM [Users] AS [w]
LEFT JOIN (
SELECT [a].[Id], [a].[AccountHolderLevel], [a].[AccountHolderType], [a].[CreatedAt], [a].[CreatedBy], [a].[DeletedAt], [a].[DeletedBy], [a].[IsDeleted], [a].[ObjectId], [a].[OperationCountryId], [a].[UpdatedAt], [a].[UpdatedBy]
FROM [AccountHolders] AS [a]
WHERE [a].[IsDeleted] = 0
) AS [t] ON [w].[Id] = [t].[ObjectId]
LEFT JOIN (
SELECT [c].[Id], [c].[ContinentId], [c].[CountryCode], [c].[CreatedAt], [c].[CreatedBy], [c].[DeletedAt], [c].[DeletedBy], [c].[ISOCode2], [c].[IsActive], [c].[IsDeleted], [c].[IsOperational], [c].[LocalCurrencyId], [c].[Name], [c].[PhoneCode], [c].[PostCodeProvider], [c].[Regex], [c].[SmsProvider], [c].[UpdatedAt], [c].[UpdatedBy]
FROM [Countries] AS [c]
WHERE [c].[IsDeleted] = 0
) AS [t0] ON [t].[OperationCountryId] = [t0].[Id]
WHERE ([w].[IsDeleted] = 0) AND ((((([w].[FirstName] LIKE #__filter_SearchTerm_0 + N''%'' AND (LEFT([w].[FirstName], LEN(#__filter_SearchTerm_0)) = #__filter_SearchTerm_0)) OR (#__filter_SearchTerm_0 = N'''')) OR (([w].[LastName] LIKE #__filter_SearchTerm_1 + N''%'' AND (LEFT([w].[LastName], LEN(#__filter_SearchTerm_1)) = #__filter_SearchTerm_1)) OR (#__filter_SearchTerm_1 = N''''))) OR (([w].[UserName] LIKE #__filter_SearchTerm_2 + N''%'' AND (LEFT([w].[UserName], LEN(#__filter_SearchTerm_2)) = #__filter_SearchTerm_2)) OR (#__filter_SearchTerm_2 = N''''))) OR EXISTS (
SELECT 1
FROM [Accounts] AS [x]
WHERE (([x].[IsDeleted] = 0) AND (([x].[AccountRef] LIKE #__filter_SearchTerm_3 + N''%'' AND (LEFT([x].[AccountRef], LEN(#__filter_SearchTerm_3)) = #__filter_SearchTerm_3)) OR (#__filter_SearchTerm_3 = N''''))) AND ([t].[Id] = [x].[AccountHolderId])))
ORDER BY [w].[LastActivity] DESC, [w].[Id], [t].[Id]',N'#__p_4 int,#__filter_SearchTerm_0 nvarchar(100),#__filter_SearchTerm_1 nvarchar(100),#__filter_SearchTerm_2 nvarchar(256),#__filter_SearchTerm_3 nvarchar(450)',#__p_4=10,#__filter_SearchTerm_0=N'james',#__filter_SearchTerm_1=N'james',#__filter_SearchTerm_2=N'james',#__filter_SearchTerm_3=N'james'
Finally this is my SQL query that returns whatever is necessary in less than 100 ms:
declare #searchTerm varchar(100) = '%james%'
select top 10
u.Id,
u.UserName,
u.FirstName,
u.LastName,
u.LastActivity,
u.CreatedAt,
a.Balance,
a.AccountRef,
ah.AccountHolderLevel,
u.Email,
r.Name
from Users u
join AccountHolders ah on ah.ObjectId = u.Id
join Accounts a on ah.Id = a.AccountHolderId
join UserRoles ur on ur.UserId = u.Id
join Roles r on r.Id = ur.RoleId
where FirstName like #searchTerm or LastName like #searchTerm or u.UserName like #searchTerm or FirstName + ' ' + LastName like #searchTerm or a.AccountRef like #searchTerm
and a.CurrencyId = ah.OperationCountryId
The columns I am searching are all indexed by the way, so that's not a problem. I know that the new EF-Core has many performance improvements. Unfortunately, I cannot update due to sheer number of breaking changes.
I am not sure splitting query into 2 (one for users and one for account) would work well, because there will be joins all over again. If I cannot find a solution using I plan converting my query to a view, but I want to do it as a last resort, since our convention is to use EF as much as possible. And I refuse to believe that EF does not have a solution. This is not actually a complex query at all and I am sure a fairly common use case.
So, what is the best way to optimize this query using EF-Core?
So, what is the best way to optimize this query using EF-Core?
Many things have changed in EF Core query pipeline since 2.1 (3.0, 3.1, 5.0 and now working on 6.0), but some general rules can be used, with the goal of getting rid of the client side query evaluation (which starting with 3.0 is not supported at all, so it's good to start preparing for the switch - support for 2.1 ends August this year).
The first would be to remove all these Include / ThenInclude. If the query is projecting the result in DTO without involving entity instances, then all these are redundant/not needed and removing them will ensure the query gets fully translated to SQL.
var query = _dbContext.Users.AsQueryable();
// Apply filters...
The next is the Roles collection. You must remove Mapper.Map call, otherwise it can't be translated. In general either use AutoMapper mappings and ProjectTo to fully handle the projection, or not use it at all (never put Map method calls inside query expression tree). According to your SQL, it should be something like this
Roles = user.UserRoles.Select(ur => ur.Role)
.Select(r => new RoleDTO { Name = r.Name })
.ToList(),
Actually EF Core will execute this as separate query (a behavior broken by "single query mode" in 3.x, and brought back optionally with 6.0 "split query mode"), so it is is important to have ToList() call at the end, otherwise you'll get N + 1 queries rather than 2.
Finally, the Single() call. It can be avoided by flattening the sub collection using correlated SelectMany, or its query syntax equivalent
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
The let statement is not mandatory, I've added it just for readability. Now you can use the range variables user, ah and a in the final select similar to table aliases in SQL.
Also since your SQL query doesn't really enforce single account match, there is no such enforcement in the LINQ query as well. If it was needed, then the equivalent of the Single can be achieved with SelectMany + Where + `Take(1), e.g.
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
.Where(a => a.CurrencyId == ah.OperationCountryId)
.Take(1)
(a mixture of query and method syntax, but LINQ allows that)
So the final query would be something like this
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
select new //UserDTO
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Username = user.UserName,
Email = user.Email,
Roles = user.UserRoles.Select(ur => ur.Role)
.Select(r => new RoleDTO { Name = r.Name })
.ToList(),
LastActivity = user.LastActivity,
CreatedAt = user.CreatedAt,
EmailConfirmed = user.EmailConfirmed,
AccountBalance = a.Balance,
AccountReference = a.AccountRef
}
and should translate to very similar to the handcrafted SQL. And hopefully execute faster similar to it.

NHibernate Linq Expression dynamic projection

How can i dynamically change the selected columns in the generated sql query when using a linq expression?
Its a new session for each time the query is executed.
Even when I set the MapExp as null after first creation an then changing the bool value to false, it still generates the column in the sql query.
The code runs in a wpf application.
System.Linq.Expressions.Expression<Func<Entity, Model>> MapExp = x => new Model
{
Id=xId,
Count= LoadFormulaField ? x.Count: null,
...
};
var result = session.Query<Entity>().Select(MapExp))
Your problem seems to be the ternary-conditional as part of the expression which is causing the "Count" column to always be queried.
One option to avoid this could be:
var query = session.Query<Entity>();
IQueryable<Model> result = null;
if (LoadFormulaField)
{
result = query.Select(x => new Model
{
Id = x.Id,
Count = x.Count,
});
}
else
{
result = query.Select(x => new Model
{
Id = x.Id,
});
}
Which would get a little less ugly if you separate in a couple of methods I think.

Convert SQL to LINQ with group by

I'm stumped trying to convert the following sql to linq:
SELECT t.* FROM(SELECT mwfieldid,MAX([TimeStamp]) AS MaxValue, BatchDocumentID
FROM mw_BatchField
GROUP BY mwfieldid,BatchDocumentID) x
JOIN mw_BatchField t ON x.mwfieldid = t.mwfieldid
AND x.MaxValue = t.TimeStamp
and x.BatchDocumentID = t.BatchDocumentID
So far I had to convert it to a stored proc to get it to work. I'd rather know how to write this correctly in linq. I tried using a sql to linq converter (http://www.sqltolinq.com/) which produced this code that had errors in it: (Are these converters any good? It didn't seem to produce anything useful with a few tries.)
From x In (
(From mw_BatchFields In db.mw_BatchFields
Group mw_BatchFields By
mw_BatchFields.MWFieldID,
mw_BatchFields.BatchDocumentID
Into g = Group
Select
MWFieldID,
MaxValue = CType(g.Max(Function(p) p.TimeStamp),DateTime?),
BatchDocumentID)
)
Join t In db.mw_BatchFields
On New With { .MWFieldID = CInt(x.MWFieldID), .MaxValue = CDate(x.MaxValue), .BatchDocumentID = CInt(x.BatchDocumentID) }
Equals New With { .MWFieldID = t.MWFieldID, .MaxValue = t.TimeStamp, .BatchDocumentID = t.BatchDocumentID }
Select
BatchFieldID = t.BatchFieldID,
BatchDocumentID = t.BatchDocumentID,
MWFieldID = t.MWFieldID,
TimeStamp = t.TimeStamp,
value = t.value,
DictionaryValue = t.DictionaryValue,
AutoFilled = t.AutoFilled,
employeeID = t.employeeID
Seems like a lot of code for such a simple query, and it doesn't compile.
So for every combination of mwfieldid and BatchDocumentID you want all columns of the row with the highest TimeStamp? This is something which is much easier to express in LINQ than SQL so I'm not surprised that an automated converter is making a meal of it.
You should be able to do:
Mw_BatchFields.GroupBy(x => new { x.Mwfieldid, x.BatchDocumentId })
.SelectMany(x => x.Where(y => y.TimeStamp == x.Max(z => z.TimeStamp)))
This (like your SQL) will return multiple rows per grouping key if there is more than one row in the group that shares the same maximum TimeStamp. If you only want row per key, you could use:
Mw_BatchFields.GroupBy(x => new { x.Mwfieldid, x.BatchDocumentId })
.Select(x => x.OrderByDescending(y => y.TimeStamp).First())
Edit:
Sorry, just twigged that you're working in VB, not C#, so not quite what you were looking for, but if you can live with the lambda syntax style, I think the above can be translated as:
Mw_BatchFields.GroupBy(Function(x) New With {x.Mwfieldid, x.BatchDocumentId}).Select(Function(x) x.OrderByDescending(Function(y) y.TimeStamp).First())
and:
Mw_BatchFields.GroupBy(Function(x) New With {x.Mwfieldid, x.BatchDocumentId}).SelectMany(Function(x) x.Where(Function(y) y.TimeStamp = x.Max(Function(z) z.TimeStamp)))

Linq query using list output as input

I am using Linqpad and have odata connection setup.
I have a query as follows
QUERY1
void Main()
{var a = from cpuid in Computers
where cpuid.DnsHostName == "xyz"
select new {
ID = cpuid.TechnicalProductsHosted.Select (x => new { Id = x.Id }),
System_Dept = cpuid.SystemDepartment,
};
Console.WriteLine(a);
}
The output : it returns 4 ids but one department which is common among all four id's. When i query otherway round i.e
QUERY2
var a = from id in TechnicalProducts
where id.Id == "ID-15784"
select new
{System_Dept = id.Computers.Select(x => x.SystemDepartment),
Support_Team = id.Computers.Select(x => x.SupportTeam)
};
Console.WriteLine(a);
The output : 4 departments for the id. I wish to have the whole list of departments in the first case. How is it possible? In query 1 Can i take id as input for System Department and query it somehow?
the output samples

Performing a left outer join when an nhibernate filter is applied

I am trying to perform a LEFT OUTER JOIN with nhibernate criteria. I also have a filter that gets applied to my queries.
The problem I have is the filter stops the left outer join working properly if the join result is null.
As a very simple example I want to return all the musicians, and if they are in a band then also their band
NHibernate generates the following sql
SELECT this_.Name, band2_.Name
FROM Musicians this_
left outer join [Band] band2_
on this_.BandID = band2_.ID
WHERE (band2_.IsDeleted = 0)
which won't return the musicians if they aren't in a band. What I want is something like
SELECT this_.Name, band2_.Name
FROM Musicians this_
left outer join [Band] band2_
on this_.BandID = band2_.ID
WHERE this_.ID = 4894 /* #p3 */
(band2_.ID IS NULL OR band2_.IsDeleted = 0)
Is this possible with nhibernate?
UPDATE
var projections = new[]
{
Projections.Property("Musician.Name").As("MusicianName"),
Projections.Property("Band.Name").As("BandName")
};
return this.sessionProvider.GetSession().CreateCriteria<Musician>("Musician")
.CreateCriteria("Musician.Band", "Band", JoinType.LeftOuterJoin)
.SetProjection(projections)
.Add(Restrictions.Eq("Musician.ID", parameters.MusicianId))
.SetResultTransformer(Transformers.AliasToBean<MusicianDetailsResult>())
.UniqueResult<MusicianDetailsResult>();
The filter is defined with FluentNHibernate
this.WithName(FilterName).WithCondition("IsDeleted = 0")
This is a bug in NHibernate.
I used proposed workaround and set useManyToOne on the filter to false. This property isn't currently in FluentNhibernate so I just do it in ExposeConfiguration
foreach (var key in cfg.FilterDefinitions.Keys)
{
filter = cfg.FilterDefinitions[key];
cfg.FilterDefinitions[key] = new FilterDefinition(
filter.FilterName,
filter.DefaultFilterCondition,
filter.ParameterTypes, false);
}
Firstly, this is much easier if you simply map Band to Musician as a reference:
public class MusicianDbMap : ClassMap<Musician>
{
public MusicianDbMap()
{
...
References(x => x.Band)
.Nullable()
.Not.LazyLoad(); // Or lazy load... either way
}
}
Then you can just run a simple query - here it is in Linq-2-NHibernate:
Session.Linq<Musician>()
.Where(x => x.Band == null || !x.Band.IsDeleted)
.ToList();
Secondly, I'm not sure about this statement of yours: "which won't return the musicians if they aren't in a band"... I'm not sure if that is correct. A left outer join should return all rows, regardless of whether they are in a band or not - are you sure that you haven't made an error somewhere else?