How can I avoid multiple calls to repositories and simplify using joins?
var members = await _groupMemberRepository.Value.GetAllGroupMembers(groupId);
var groupMemberModels = members as IGroupMemberModel[] ?? members.ToArray();
var users = await _userManager.Value.GetUsersList(groupMemberModels.Select(x => x.UserId));
var roles = await _userAccountBusinessLogic.GetRoles(null);
return (from m in groupMemberModels
join u in users
on m.UserId equals u.Id
select
new Member
{
UserId = m.UserId,
FirstName = u.FirstName,
LastName = u.LastName,
Username = u.UserName,
GroupUserType = m.GroupUserType,
LastLoggedIn = u.LastLoggedIn,
Roles = MeeRoleParser.ParseList(u.Roles.Select(x =>
roles.Where(r => r.Id == x.RoleId).Select(r => r.Name).FirstOrDefault()).ToList())
}).ToArray();
The above query calls different repositories. I am not getting an idea how to do a join and simplify the call
Related
I am trying to do a LINQ query on several Mongo collections. All the collections have to be joined based on ApplicationId and an outer Join has to be done - so that persons that have no statuses are returned as well.
The JOIN part and everything around it works as expected. The problem is that when I add a filter to one of the collections, the whole thing breaks
An exception of type 'System.ArgumentException' occurred in System.Linq.Expressions.dll but was not handled in user code: 'Expression of type 'System.Collections.Generic.IEnumerable`1[CDM.Person]' cannot be used for parameter of type 'System.Linq.IQueryable`1[CDM.Person]' of method 'System.Linq.IQueryable`1[CDM.Person] Where[Person](System.Linq.IQueryable`1[CDM.Person], System.Linq.Expressions.Expression`1[System.Func`2[CDM.Person,System.Boolean]])''
Here is my query
var applications = _dbContext.GetCollection<Application>(typeof(Application).Name).AsQueryable().Where(
x => x.OrganizationID == TokenContext.OrganizationID);
var persons = _dbContext.GetCollection<Person>(typeof(Person).Name).AsQueryable().Where(p =>p.FirstName == "j");
var statuses = _dbContext.GetCollection<ApplicationStatus>(typeof(ApplicationStatus).Name).AsQueryable();
var mortgages = _dbContext.GetCollection<Mortgage>(typeof(Mortgage).Name).AsQueryable();
var statusQuery = from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID
join s in statuses on a.ApplicationID equals s.ApplicationID into pas
join m in mortgages on a.ApplicationID equals m.ApplicationID into morgs
from subWHatever in pas.DefaultIfEmpty()
select new ApplicationStatusProjection
{
ApplicationId = a.ApplicationID,
FirstName = p.FirstName,
LastName = p.Surname,
Prefix = p.Prefix,
DateOfBirth = p.DateOfBirth,
Initials = p.Initials,
PostalCode = p.Addresses.First().PostalCode,
MortgageAmount = morgs.Sum(i => i.MortgageTotal) ?? 0,
StatusExpireAt = subWHatever.ExpireAt ?? DateTime.MinValue,
StatusMessageText = subWHatever.MessageText ?? "",
StatusMessage = subWHatever.MessageStatus ?? ""
};
if (!String.IsNullOrEmpty(orderBy))
{
statusQuery = statusQuery?.OrderBy(orderBy);
}
if (nrOfRecords != null)
{
statusQuery = statusQuery?.Take(nrOfRecords.Value);
}
// Execute the query
var result = statusQuery?.ToList();
return result;
I followed the guidelines here https://mongodb.github.io/mongo-csharp-driver/2.6/reference/driver/crud/linq/ and I also tried this
var statusQuery =
from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID into pa
from paObject in pa.DefaultIfEmpty()
join s in statuses on paObject.ApplicationID equals s.ApplicationID into pas
But I got the same error as before.
Thank you in advance.
So after may tryouts I have discovered that you cannot filter before the join because of the way the LINQ query is translated to Mongo query.
The fix for this is to have the filtering afterwards, on the statusQuery object. In that case, the filtering has to happen on the projected object (so a new filter is needed).
See below how I solved it:
//get collections
var applications = _dbContext.GetCollection<Application>(typeof(Application).Name).AsQueryable().Where(
x => x.OrganizationID == TokenContext.OrganizationID);
var persons = _dbContext.GetCollection<Person>(typeof(Person).Name).AsQueryable();
var statuses = _dbContext.GetCollection<ApplicationStatus>(typeof(ApplicationStatus).Name).AsQueryable();
var mortgages = _dbContext.GetCollection<Mortgage>(typeof(Mortgage).Name).AsQueryable();
//query
var query = from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID
join s in statuses on a.ApplicationID equals s.ApplicationID into applicationStatusView
join m in mortgages on a.ApplicationID equals m.ApplicationID into morgs
from subStatus in applicationStatusView.DefaultIfEmpty()
select new ApplicationStatusProjection
{
ApplicationId = a.ApplicationID,
FirstName = p.FirstName,
LastName = p.Surname,
Prefix = p.Prefix,
DateOfBirth = p.DateOfBirth,
Initials = p.Initials,
Addresses = p.Addresses ?? new List<Address>(),
PostalCode = p.Addresses.First().PostalCode,
MortgageAmount = morgs.Sum(i => i.MortgageTotal) ?? 0,
StatusExpireAt = subStatus.ExpireAt ?? DateTime.MinValue,
StatusMessageText = subStatus.MessageText ?? "",
StatusMessage = subStatus.MessageStatus ?? "",
StatusDate = subStatus.StatusDate
};
//filter & order
var filteredResult = ApplyFilters(query, searchCriteria);
if (!String.IsNullOrEmpty(orderBy))
{
filteredResult = filteredResult?.OrderBy(orderBy);
}
if (nrOfRecords != null)
{
filteredResult = filteredResult?.Take(nrOfRecords.Value);
}
// Execute the query
var result = filteredResult?.ToList();
return result;
And applying the filters (there is probably a smarter way to do this):
private IQueryable<ApplicationStatusProjection> ApplyFilters(IQueryable<ApplicationStatusProjection> query, ApplicationStatusProjectionSearch searchCriteria)
{
if (!string.IsNullOrEmpty(searchCriteria.FirstName))
{
query = query.Where(x => x.FirstName.ToLower().StartsWith(searchCriteria.FirstName));
}
if (!string.IsNullOrEmpty(searchCriteria.LastName))
{
query = query.Where(x => x.LastName.ToLower().StartsWith(searchCriteria.LastName));
}
if (!string.IsNullOrEmpty(searchCriteria.PostalCode))
{
query = query.Where(x => x.Addresses.Any(a => a.PostalCode.ToLower().StartsWith(searchCriteria.PostalCode)));
}
//other irrelevant filters
return query;
}
Which of the following is the best way to read properties from a related table using LINQ to SQL?
var users = (from user in db.users.Where(u => u.Id > 10)
select new User
{
UserName = u.UserName
UserId = u.Id,
Address1 = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).Address1,
Address2 = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).Address2,
Address3 = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).Address3,
City = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).City,
State = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).State,
Zip = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress).Zip
}).ToList();
OR
var users = (from user in db.users.Where(u => u.Id > 10)
select new
{
UserName = u.UserName
UserId = u.Id,
Address = u.UserAddress.FirstOrDefault(a => a.IsHomeAddress)
}).ToList();
users.ForEach(u => {
u.Address1 = u.Address.Address1,
u.Address2 = u.Address.Address2,
u.Address3 = u.Address.Address3,
u.City = u.Address.City,
u.State = u.Address.State,
u.Zip = u.Address.Zip
});
Or is there a better way?
I'd recommend using let, i.e.:
var users = (
from user in db.users
where user.Id > 10
let homeAddress = user.UserAddress.First(a => a.IsHomeAddress)
select new User
{
UserName = user.UserName
UserId = user.Id,
Address1 = homeAddress.Address1,
Address2 = homeAddress.Address2,
Address3 = homeAddress.Address3,
City = homeAddress.City,
State = homeAddress.State,
Zip = homeAddress.Zip
}).ToList();
I am trying to pass data from a view model which derives from 2 models. The controller looks like this
public ActionResult SessionDetails()
{
var Sessions = (from a in db.Appointments
join c in db.Clients
on a.clientid equals c.id into SessionList
from c in SessionList.DefaultIfEmpty()
select new SessionViewModel()
{
id = a.id,
sessionnotes = a.sessionnotes,
firstname = c.firstname,
date = a.date,
}).ToList()
.Select(x => new SessionViewModel()
{
id = a.id,
sessionnotes = a.sessionnotes,
firstname = c.firstname,
date = a.date,
});
return View(Sessions);
}
It is coming up with errors saying the name "a" does not exist in the current context? Any ideas as to what is going on?
I am really new to this, I followed these instructions using website http://techfunda.com/howto/262/list-data-using-viewmodel which does what I want SessionDetails to do.
I already have a view for this.
UPDATE
I have made following changes:
public ActionResult SessionDetails()
{
var Sessions = (from a in db.Appointments
join c in db.Clients
on a.clientid equals c.id into SessionList
from c in SessionList.DefaultIfEmpty()
select new SessionViewModel()
{
id = a.id,
sessionnotes = a.sessionnotes,
firstname = c.firstname,
date = a.date,
}).ToList();
return View(Sessions);
}
But when i run it, i get this: an exception of type System.NotSupportedException occurred in EntityFramework.SqlServer.dll but was not handled in user code.
Additional information: the entity or complex type fypag.Models.SessionViewModel cannot be constructed in a LINQ to Entities query.
I think your model should look like this.
var Sessions = (from a in db.Appointments
join c in db.Clients
on a.clientid equals c.id into SessionList
from c in SessionList.DefaultIfEmpty()
select new SessionViewModel()
{
id = a.id,
sessionnotes = a.sessionnotes,
firstname = c.firstname,
date = a.date,
}).ToList()
.Select(x => new SessionViewModel()
{
id = x.id,
sessionnotes = x.sessionnotes,
firstname = x.firstname,
date = x.date,
});
UPDATE:
var Sessions = (from a in db.Appointments
join c in db.Clients
on a.clientid equals c.id into SessionList
from c in SessionList.DefaultIfEmpty()
.Select(y => new SessionViewModel()
{
id = y.id,
sessionnotes = y.sessionnotes,
firstname = y.firstname,
date = y.date,
}).ToList();
We're currently migrating our production platform to Azure, and as such I need to move over all of our support tools. Previously, we relied heavily on data adapters and stored procedures, and many of these stored procedures performed cross-database joins.
With our migration to Azure, none of these cross-database joins are operational. I tried moving our data adapters to Entity Framework, but I cannot seem to make them work. Instead, I get an error stating that cross-context joins are not allowed. Many of these queries rely on data from multiple databases, so I'm just trying to figure out what the best method of approach is to get this operational.
I've looked at several of the other questions asking for similar solutions, but none of them seem terribly applicable to my solution.
For example, here's one of the more simple queries in SQL:
USE CustomerDB1234
SELECT DISTINCT u.[UserID]
,u.[UserLogin]
,u.[UserPhoneNumber]
,u.[UserPasswordHash]
, ISNULL(gl.[gl_login_name],'* no global login ID') AS [gl_login_name]
,gl.[gl_password_hash]
,gl.[gl_GUID]
,gl.[gl_Email_Validated]
,u.[usr_unit_set_id]
,oob.[oob_org_id]
FROM [dbo].[User] u WITH (NOLOCK)
LEFT JOIN [dbo].[OrganizationObjectBridge] oob WITH (NOLOCK) ON oob.[oob_object_type_id] = 9 AND oob.[oob_object_id]= u.UserID
LEFT JOIN [MainServer].[MainDb].[dbo].[GlobalLoginCustomerBridge] glcb WITH (NOLOCK) ON glcb.[glcbr_user_id] = u.UserID
AND glcb.[glcbr_customer_id] = dbo.efnGetCustomerID()
LEFT JOIN [MainServer].[MainDb].[dbo].[GlobalLogin] gl WITH (NOLOCK) ON gl.[gl_id] = glcb.[glcbr_gl_id]
WHERE ([UserID] = #userID OR #userID IS NULL)
AND ([UserDisabled] = #isDisabled OR #isDisabled IS NULL)
ORDER BY [gl_login_name]
And in Linq, it would look similar to:
List<User2> userList = new List<User2>();
using (var e = new eContext())
using (var context = new CustomerContext(CustomerID))
{
var databaseConnections = e.DatabaseConnectionStrings;
var customer = e.Customers.Select(n => new
{
ID = n.CustomerID,
Name = n.CustomerName,
Email = n.CustomerEmail,
Website = n.CustomerWWW,
Logo = n.CustomerLogo,
DatabaseConnectionName = databaseConnections.FirstOrDefault(d => d.DatabaseConnectionID == n.DatabaseConnectionID).DatabaseConnectionName,
DatabaseConnectionString = databaseConnections.FirstOrDefault(d => d.DatabaseConnectionID == n.DatabaseConnectionID).DatabaseConnectionString1,
AccountNumber = n.CustomerAcctNumber
}).FirstOrDefault(n => n.ID == CustomerID);
userList = context.Users
.Join(e.GlobalLoginCustomerBridges,
u => u.UserID,
glcb => glcb.glcbr_user_id,
(u, glcb) => new { u, glcb })
.Where(n => n.glcb.glcbr_customer_id == CustomerID)
.Join(e.GlobalLogins,
glcb => glcb.glcb.glcbr_gl_id,
gl => gl.gl_id,
(glcb, gl) => new { glcb, gl })
.Join(context.OrganizationObjectBridges,
glcb => glcb.glcb.u.UserID,
oob => oob.oob_object_id,
(glcb,oob) => new {glcb, oob})
.Where(n=>n.oob.oob_object_type_id == 9)
.Select(n => new
{
ID = n.glcb.glcb.u.UserID,
GlobalLogin = n.glcb.gl.gl_login_name,
FirstName = n.glcb.glcb.u.UserFirstName,
MiddleName = n.glcb.glcb.u.UserMiddleName,
LastName = n.glcb.glcb.u.UserLastName,
GUID = n.glcb.gl.gl_GUID,
UserID = n.glcb.gl.gl_id,
HasSHA256Hash = n.glcb.gl.gl_password_hash_sha256 == null,
Customer = customer,
Organization = context.Organizations
.Select(o => new
{
ID = o.org_id,
Name = o.org_name,
ParentID = o.org_parent_id,
ExternalID = o.org_external_id,
Default = o.org_default,
Logo = o.org_logo,
URL = o.org_url,
PeerGroupID = o.org_peer_grp_id,
ExternalInfo = o.org_external_info
}).Cast<Organization2>().FirstOrDefault(o=>o.ID == n.oob.oob_org_id)
}).Cast<User2>().ToList();
}
Two approaches:
Perform the joins in the application in memory. Depending on the query this is more or less efficient. Also, the code changes required range from tiny to terrible.
Merge databases together. Having many databases without a physical reason for that is an anti-pattern. Databases are not logical units of application modularization. Document modularization through schemas or table name prefixes. Databases are physical units of deployment.
Third approach: Run a SQL Server in a VM.
By using the information posted by the other answer, I came up with a solution that appears to work. Granted, it doesn't appear nearly as efficient or as concise as cross-database joins, but it gets the job done.
using (var context = new CustomerContext(CustomerID))
using (var e = new eContext())
{
var globalUserList = e.GlobalLoginCustomerBridges
.Join(e.GlobalLogins,
glcb => glcb.glcbr_gl_id,
gl => gl.gl_id,
(glcb, gl) => new { glcb, gl })
.Where(n => n.glcb.glcbr_customer_id == CustomerID)
.Select(n => new User2
{
ID = (int)n.glcb.glcbr_user_id,
GlobalLogin = n.gl.gl_login_name,
GUID = n.gl.gl_GUID
}).ToList();
var customer = e.Customers
.Join(e.DatabaseConnectionStrings,
c => c.DatabaseConnectionID,
d => d.DatabaseConnectionID,
(c, d) => new { c, d })
.Select(n => new Customer2
{
ID = n.c.CustomerID,
Name = n.c.CustomerName,
DatabaseConnectionName = n.d.DatabaseConnectionName,
DatabaseConnectionString = n.d.DatabaseConnectionString1,
GUID = n.c.cust_guid,
}).ToList().FirstOrDefault(n => n.ID == CustomerID);
var orgs = context.Organizations
.Select(o => new Organization2
{
ID = o.org_id,
Name = o.org_name,
}).ToList();
var users = context.Users
.Select(n => new User2
{
ID = n.UserID,
FirstName = n.UserFirstName,
}).ToList();
var userList = users
.Join(globalUserList,
u => u.ID,
gl => gl.ID,
(u, gl) => new { u, gl })
.Join(context.OrganizationObjectBridges,
u => u.u.ID,
oob => oob.oob_object_id,
(u, oob) => new { u, oob })
.Where(o => o.oob.oob_object_type_id == 9)
.Select(n => new User2
{
ID = n.u.u.ID,
GlobalLogin = n.u.gl.GlobalLogin,
FirstName = n.u.u.FirstName,
GUID = n.u.gl.GUID,
Customer = customer,
Organization = orgs.FirstOrDefault(o => o.ID == n.oob.oob_org_id)
}).Where(n => !isDisabled != null && n.Disabled == isDisabled).ToList();
return userList;
}
I am trying to convert this T-SQL to a Linq To SQL but can't work out the group by aggregate functions. Any help welcome.
select c.ClientID, GivenName, Surname, max(a.Address), max(t.Value)
from Client c
left join ClientAddress a on c.ClientID = a.ClientID
left join ClientContact t on c.ClientID = t.ClientID
group by c.ClientID, GivenName, Surname
To group by a composite key, you typically use an anonymous type:
var qry = from x in someSource
group x by new { x.ClientID, x.GivenName, x.Surname } into grp
select new { grp.Key, Address = grp.Max(x => x.Address),
Value = grp.Max(x => x.Value) };
The exact answer I came up with was
public IQueryable<ClientSearchDTO> GetClientsDTO()
{
return (from client in this.Context.Clients
join address in this.Context.ClientAddresses on client.ClientID equals address.ClientID
join contact in this.Context.ClientContacts on client.ClientID equals contact.ClientID
where contact.ContactType == "Phone"
group client by new { client.ClientID, client.Surname, client.GivenName } into clientGroup
select new ClientSearchDTO()
{
ClientID = clientGroup.Key.ClientID,
Surname = clientGroup.Key.Surname,
GivenName = clientGroup.Key.GivenName,
Address = clientGroup.Max(x => x.ClientAddresses.FirstOrDefault().Address),
PhoneNumber = clientGroup.Max(x => x.ClientContacts.FirstOrDefault().Value)
});
}