I've got database table that stores system logs. This table has columns SourceContext and ObjectId. SourceContext stores information in what context should I interpret this log. So if SourceContext equals "Product", that means that this particular log in about "Product". Then, ObjectId is exact id of that object (e.g. id of "Product").
I needed to display logs to my customer. But instead of ObjectId I needed to display real object name from other table in database. This is what I've managed to create:
var query = from l in db.Logs
join c in db.Client on new { Key1 = l.SourceContext, Key2 = l.ObjectId } equals new { Key1 = "Client", Key2 = (int?)c.id } into c_result
from c_r in c_result.DefaultIfEmpty()
join p in db.Product on new { Key1 = l.SourceContext, Key2 = l.ObjectId } equals new { Key1 = "Product", Key2 = (int?)p.id } into p_result
from p_r in p_result.DefaultIfEmpty()
join w in db.Warehouse on new { Key1 = l.SourceContext, Key2 = l.ObjectId } equals new { Key1 = "Warehouse", Key2 = (int?)w.id } into w_result
from w_r in w_result.DefaultIfEmpty()
join i in db.Invoice on new { Key1 = l.SourceContext, Key2 = l.ObjectId } equals new { Key1 = "Invoice", Key2 = (int?)i.id } into i_result
from i_r in i_result.DefaultIfEmpty()
select new LogsViewModel { Log = l, Name = c_r.name + "" + p_r.name + "" + w_r.name + "" + i_r.invoice_number, UserName = null };
It works fine, but now I need to conditionally disable some of those joins (optimalization reasons, not all contexts requre to load object names from some tables). So I decided to convert Linq query syntaxt to method syntax.
Here is what I want to accomplish:
public static IQueryable<LogsViewModel> GetLogs(ApplicationDbContext db)
{
var query = from l in db.Logs
select new LogsViewModel { Log = l, Name = "", UserName = null };
// lines below will be split in switch/case block in the future, to conditionally disable them
query = GetUserNames(db, query);
query = GetInvoiceNames(db, query);
query = GetWarehouseNames(db, query);
// etc...
return query;
}
So I want to split my query into methods. Typical method would look like:
public static IQueryable<LogsViewModel> GetInvoiceNames(ApplicationDbContext db, IQueryable<LogsViewModel> query)
{
return query.GroupJoin(db.Invoice.DefaultIfEmpty(), logsVM => logsVM.Log.ObjectId, invoice => invoice.id, (logsVM, invoice) => new LogsViewModel
{
Log = logsVM.Log,
Name = logsVM.Name + "" + invoice.FirstOrDefault().invoice_number,
UserName = logsVM.UserName
});
}
The problem with GetInvoiceNames method is that it joins all logs with "Invoice" table, no matter SourceContext equals "Invoice" or not. I can't use Where clause or SelectMany because it would affect other logs. So in other words: I need to add a condition to GroupJoin so it will join all logs where SourceContext equals some specific value. Do you have any ideas how to solve it?
Related
So I have the list that I want to join against a table on the db.
I am getting the error Unable to create a constant value of type, what am I doing wrong?
List<info.user> studyAccessList = new List<info.User>();
using (var Db = new Entities())
{
//get users from USER table
var UserDbQry = (from data in Db.USER
where data.System == "something" & data.Active == true
select data);
//outer join
var joinQry =
from s1 in UserDbQry
join t2 in studyAccessList
on new
{
Study = s1.Study,
Email = s1.Email
}
equals new
{
Study = t2.Study,
Email = t2.Email
}
into rs
from r in rs.DefaultIfEmpty()
//where r == null
select s1;
foreach (var inactiveUser in joinQry)
{
//add tag for system.
//create xml
string xml = String.Format("<User><Study>{0}</Study><Email>{1}</Email><Active>false</Active></User>", inactiveUser.Study, inactiveUser.Email);
recordCount = recordCount + ProcessXml(wsu, xml, log.ErrorMsgPrefix);
}
}
I needed to change the first query to a list then the second query was able to pick it up.
//get users from USER table
var UserDbQry = (from data in Db.USER
where data.System == "something" & data.Active == true
select data).ToList;
//outer join
var joinQry =
from s1 in UserDbQry
join t2 in studyAccessList
on new
{
Study = s1.Study,
Email = s1.Email
}
equals new
{
Study = t2.Study,
Email = t2.Email
}
into rs
from r in rs.DefaultIfEmpty()
//where r == null
select s1;
I am currently using Linq to join two tables together, mainTable and selectTable, they are joined on mainTable.ID = selectTable.mtID. I am trying to include a third table, myTable, that is joined on selectTable.ID = myTable.selID. There will be many records in myTable for one ID from selectTable so I'm trying to get List<myTable>. This is what I have so far that works:
public async Task<List<mainTableDto>> listAll()
{
var db = _repository.DbContext;
var result = await ( from mt in db.mainTable
join sel in db.selectTable
on mt.ID equals sel.mtID
select new mainTableDto
{
ID = mt.ID,
createDate = mt.createDate,
selectTable = new selectTableDto
{
ID = sel.ID
name = sel.name
}
}
}).ToListAsync;
return result;
I've tested getting data from selectTableDto with List< myTableDto> and it works.
I'm a little stuck on how to include a List<myTableDto> into this nested call. I've tried:
join sel in db.selectTableInclude(x=>x.myTableDto)
But when I do this I don't get the info from myTableDto and just get null instead (I've put data in the DB so there should be something)
I've also tried:
join sel in db.selectTable
on mt.ID equals sel.mtID
join my in db.myTable
on sel.ID equals my.selID
selectTable = new selectTableDto
{
ID = sel.ID
name = sel.name
myTableDto = new List<myTableDto>
{
ID = my.ID
}
}
But when I do this it says "ID is not a member of myTableDTO".
Any advice on what I'm doing wrong?
I believe you want a groupjoin (method syntax) or into (query syntax)
This is query syntax:
from mt in db.mainTable
join sel in db.selectTable
on mt.ID equals sel.mtID
into mainTableSels
select new mainTableDto
{
ID = mt.ID,
createDate = mt.createDate,
selectTable = from mts in mainTableSels select new selectTableDto
{
ID = mts.ID
name = mts.name
}
}
Though I do personally prefer a hybrid query/method syntax:
from mt in db.mainTable
join sel in db.selectTable
on mt.ID equals sel.mtID
into mainTableSels
select new mainTableDto
{
ID = mt.ID,
createDate = mt.createDate,
selectTable = mainTableSels.Select(mts => new selectTableDto
{
ID = mts.ID
name = mts.name
})
}
I'm not clear on what type your mainTableDto.selectTable property is; if it's an array or list you'll need a ToArray/ToList. If it's IEnumerable then it should work without
I need to build a search query with dynamic parameters in net core 3.0.
IQueryable<UserDto> query =
from user in dbContext.DB_USER
join items in dbContext.DB__ITEMS on user.IdItem equals items.IdItem
join cars in dbContext.DB_CARS on user.IdCars equals cars.IdItem
join statsCar in dbContext.DB_STATS_CARS on cars.IdCars equals statsCar.Id
select new UserDto
{
Id = user.Id,
Name = user.Name,
Data = user.Data.HasValue ? user.Data.Value.ToUnixTime() : default(long?),
Lvl = user.L,
Items = new ItemsUdo
{
Id = items.Id,
Type = items.Type,
Value = items.Value
},
Cars = new CarsDto
{
Id = cars.Id,
Model = cars.model,
Color = cars.Color
}
};
I would like to add search parameters like user name, items type, cars model and data from user. I tried to add "where" before 'select new UserDto' but not always user will provide all search parameters. If I give below:
if(fromSearch.UserName != null && fromSearch.UserName.Lenght > 0)
{
query = query.Where(u => u.Name == fromSearch.UserName);
}
it works(on user.data does not work) but is it correct? How to do this in linq query?
Do something like this:
IQueryable<UserDto> query =
from user in dbContext.DB_USER
join items in dbContext.DB__ITEMS on user.IdItem equals items.IdItem
join cars in dbContext.DB_CARS on user.IdCars equals cars.IdItem
join statsCar in dbContext.DB_STATS_CARS on cars.IdCars equals statsCar.Id;
select new UserDto
{
Id = user.Id,
Name = user.Name,
Data = user.Data.HasValue ? user.Data.Value.ToUnixTime() : default(long?),
Lvl = user.L,
Items = new ItemsUdo
{
Id = items.Id,
Type = items.Type,
Value = items.Value
},
Cars = new CarsDto
{
Id = cars.Id,
Model = cars.model,
Color = cars.Color
}
};
if(!string.IsNullOrWhitespace(username))
query = query.Where(ud => ud.Name == username);
if(!string.IsNullOrWhitespace(itemtype))
query = query.Where(ud => ud.Items.Any(i => i.Type == itemtype));
if(!string.IsNullOrWhitespace(carmodel))
query = query.Where(ud => ud.Cars.Any(c => c.Model == carmodel));
etc. These will work like AND; if you specify a username and an itemtype you get only those users named that, with that item type somewhere in the items list.. etc
I have 3 tables structured in the following way:
CREATE TABLE [User](
Id int NOT NULL,
Name varchar(50)
PRIMARY KEY (Id)
)
CREATE TABLE [Role](
Id int NOT NULL,
UserId int NOT NULL,
Name varchar(50),
PRIMARY KEY (Id),
FOREIGN KEY (UserId) REFERENCES [User](Id)
)
CREATE TABLE [Description](
Id int NOT NULL,
RoleId int NOT NULL,
Name varchar(50)
FOREIGN KEY (RoleId) REFERENCES [Role](Id)
)
As you can see it is one to many relationship nested twice. In the code I have the following classes to represent them:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Role> Roles { get; set; }
}
public class Role
{
public int Id { get; set; }
public int UserId { get; set; }
public string Name { get; set; }
public IEnumerable<Description> Descriptions { get; set; }
}
public class Description
{
public int Id { get; set; }
public int RoleId { get; set; }
public string Name { get; set; }
}
Now I need to query for the user and also get all fields that come with it. I have figured out a way to do that using QueryMultiple such as this:
var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var result = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var users = result.Read<User>().FirstOrDefault();
var roles = result.Read<Role>();
var descriptions = result.Read<Description>();
if (users != null && roles != null)
{
users.Roles = roles;
Console.WriteLine("User: " + users.Name);
foreach (var role in users.Roles)
{
Console.WriteLine("Role: " + role.Name);
if (descriptions != null)
{
role.Descriptions = descriptions.Where(d => d.RoleId == role.Id);
foreach (var roleDescription in role.Descriptions)
{
Console.WriteLine("Description: " + roleDescription.Name);
}
}
}
}
}
The result is:
User: Bob
Role: Tester
Description: Tester First Description
Description: Tester Second Description
Description: Tester Third Description
Role: Manager
Description: Manager First Description
Description: Manager Second Description
Description: Manager Third Description
Role: Programmer
Description: Programmer First Description
Description: Programmer Second Description
Description: Programmer Third Description
Main Question:
While the above code works it feels too messy. I was wondering if there is a better/easier way to achieve this?
Bonus Points:
Please also feel free to suggest a better way to query this than using inner joins. My goal is to improve performance.
EDIT:
I came up with option two as well, but again I don't think its a good solution. With option 2 I create a 4th object that will contain results of the 3 objects combined such as this:
public class Combination
{
public int UserId { get; set; }
public string UserName { get; set; }
public int RoleId { get; set; }
public string RoleName { get; set; }
public int DescriptionId { get; set; }
public string DescriptionName { get; set; }
}
Then I process it like this:
var queryFour = "SELECT u.Id as 'UserId', u.Name as 'UserName', r.Id as 'RoleId', r.Name as 'RoleName', d.Id as 'DescriptionId', d.Name as 'DescriptionName' FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var myUser = new User();
var result = con.Query<Combination>(queryFour);
if (result != null)
{
var user = result.FirstOrDefault();
myUser.Id = user.UserId;
myUser.Name = user.UserName;
var roles = result.GroupBy(x => x.RoleId).Select(x => x.FirstOrDefault());
var myRoles = new List<Role>();
if (roles != null)
{
foreach (var role in roles)
{
var myRole = new Role
{
Id = role.RoleId,
Name = role.RoleName
};
var descriptions = result.Where(x => x.RoleId == myRole.Id);
var descList = new List<Description>();
foreach (var description in descriptions)
{
var desc = new Description
{
Id = description.DescriptionId,
RoleId = description.RoleId,
Name = description.DescriptionName
};
descList.Add(desc);
}
myRole.Descriptions = descList;
myRoles.Add(myRole);
}
}
myUser.Roles = myRoles;
}
Console.WriteLine("User: " + myUser.Name);
foreach (var myUserRole in myUser.Roles)
{
Console.WriteLine("Role: " + myUserRole.Name);
foreach (var description in myUserRole.Descriptions)
{
Console.WriteLine("Description: " + description.Name);
}
}
}
The resulting output is the same in both methods and the second method uses 1 query as opposed to 3.
EDIT 2: Something to consider, my data for these 3 tables are updated often.
EDIT 3:
private static void SqlTest()
{
using (IDbConnection connection = new SqlConnection())
{
var queryOne = "SELECT Id FROM [TestTable] With(nolock) WHERE Id = 1";
var queryTwo = "SELECT B.Id, B.TestTableId FROM [TestTable] A With(nolock) INNER JOIN [TestTable2] B With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";
var queryThree = "SELECT C.Id, C.TestTable2Id FROM [TestTable3] C With(nolock) INNER JOIN [TestTable2] B With(nolock) ON B.Id = C.TestTable2Id INNER JOIN [TestTable] A With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";
var gridReader = connection.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var user = gridReader.Read<Class1>().FirstOrDefault();
var roles = gridReader.Read<Class2>().ToList();
var descriptions = gridReader.Read<Class3>().ToLookup(d => d.Id);
user.Roles= roles;
user.Roles.ForEach(r => r.Properties = descriptions[r.Id].ToList());
}
}
Your first option can be simplified to the following. It removes the deep control structure nesting. You can generalize it to deeper nesting without adding more nesting/complexity.
var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var gridReader = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var user = gridReader.Read<User>().FirstOrDefault();
if (user == null)
{
return;
}
var roles = gridReader.Read<Role>().ToList();
var descriptions = gridReader.Read<Description>().ToLookup(d => d.RoleId);
user.Roles = roles;
roles.ForEach(r => r.Descriptions = descriptions[r.Id]);
}
Performance-wise it behaves the same as your first option.
I would't go with your second option (or the similar view based one): in case you have R roles, and on average D descriptions per roles, you would query 6*R*D cells instead of 2+3*R+3*D. If R and D are high, you'd be querying a lot more data, and the cost of deserialization will be to high compared to running 3 queries instead of 1.
An alternative approach (Working code snippet with .Net Core):
Why not create a database View like this -
create view myview
As
SELECT u.Id as 'UserId', u.Name as 'UserName', r.Id as 'RoleId',
r.Name as 'RoleName', d.Id as 'DescriptionId', d.Name as 'DescriptionName'
FROM User u INNER JOIN Role r ON u.Id = r.UserId
INNER JOIN Description d ON r.Id = d.RoleId;
Then you can use your cleaner query as below :
public List<Combination> GetData(int userId)
{
String query = "select * from myview" + " where userId = " + userId + ";";
using (System.Data.Common.DbConnection _Connection = database.Connection)
{
_Connection.Open();
return _Connection.Query<Combination>(query).ToList();
}
}
And your processing code will look like this :
[Note: This can be enhanced even further.]
public static void process (List<Combination> list)
{
User myUser = new User(); myUser.Id = list[0].UserId; myUser.Name = list[0].UserName;
var myroles = new List<Role>(); var r = new Role(); string currentRole = list[0].RoleName;
var descList = new List<Description>(); var d = new Description();
// All stuff done in a single loop.
foreach (var v in list)
{
d = new Description() { Id = v.DescriptionId, RoleId = v.RoleId, Name = v.DescriptionName };
if (currentRole == v.RoleName)
{
r = new Role() { Id = v.RoleId, Name = v.RoleName, UserId = v.UserId, Descriptions = descList };
descList.Add(d);
}
else
{
myroles.Add(r);
descList = new List<Description>(); descList.Add(d);
currentRole = v.RoleName;
}
}
myroles.Add(r);
myUser.Roles = myroles;
Console.WriteLine("User: " + myUser.Name);
foreach (var myUserRole in myUser.Roles)
{
Console.WriteLine("Role: " + myUserRole.Name);
foreach (var description in myUserRole.Descriptions)
{
Console.WriteLine("Description: " + description.Name);
}
}
}
From the performance standpoint, Inner Joins are better than other forms of query like subquery, Co-related subquery, etc. But ultimately everything boils down to the SQL execution plan.
The tl;dr of my answer to you:
Abstract. You should break-up things into the different jobs they do. C# gives you functions and classes, they can help clean up code a lot.
If you can look at a chunk of code and say "this chunk is basically taking something and doing blah with it to get some result..", then make a function called SomeResult DoBlah(SomeThing thing)
Don't let a little more code scare you, it may make the big picture seem more complex at first glance. But it will make small chunks of code way easier to understand, and that makes the big picture easier to understand.
My full answer:
There are a couple of things you could do that would make this cleaner. Probably the biggest thing you could do for yourself, is to separate out some of the responsibilities/concerns. You have a lot of different things going on, and all of it is in one place, relying on everything staying exactly like it is, forever. If you change something, you will have to change things all over the place to make sure everything works. This is called "coupling", and coupling is bad... um-kay.
At the broadest level, you have three different things going on: 1. You are trying to apply logic to users and roles (i.e. You want to get info on all of the users in a role.) 2. You are trying to pull info from a database, and fit it into User, Role, and Description objects. 3. You are trying to display information about Users, Roles, and Descriptions.
So if I were you, I would have at least 3 classes right there. \
Program that is the driver of your application. It should have a main function and should use the other 3 classes.
DisplayManager that can be responsible for writing info out to a console. It should be constructed with an IEnumerable<User> and should have a public method public void DisplayInfo() that will write its list of users to a console.
UserDataAccess that can be used for fetching a list of users from a database. I would have it expose a public method for public IEnumerable<User> GetUsersByRoleID(int roleID)
This way, your main program would look something like this:
public static void Main(String[] args)
{
int roleID = 6;
UserDataAccess dataAccess = new UserDataAccess();
IEnumerable<User> usersInRole = dataAccess.GetUsersByRoleID(roleID);
DisplayManager displayManager = new DisplayManager(usersInRole);
displayManager.DisplayInfo();
}
Honestly, this is a simplified version of what I would do if the problem had more functionality then just getting a single specific piece of info. You should look into SOLID principles. Specifically "Single Responsibility" and "Dependency Inversion", these principles can really clean up some "messy"/"smelly" code.
Next, as far as your actually data access goes, since you are using dapper, you should be able to map nested objects using build in dapper functionality. I believe this link should be of some help to you in this aspect.
I have been given the following skeleton code:
var test = SqlCompact(
"OrderID", "ASC", 5, 2,
out count, out sortCriteria, out sql);
This calls the SqlCompact method which performs joins of tables orders, employees and customers & then orders by the inputs e.g. "ASC" and "Column Name".
I have got the join query working but not sure how to order the results according to the input. This is my code for SqlCompact Method:
public List<MyJoin> SqlCompact(
string sort, string sortDir, int rowsPerPage, int page,
out int count, out string sortCriteria, out string sql) {
var cec = new SqlCeConnection(
string.Format(#"Data Source={0}\Northwind.sdf", Path));
var nwd = new DataContext(cec);
nwd.Log = new StringWriter();
var orders = nwd.GetTable<Order>();
var employees = nwd.GetTable<Employee>(); //
var customers = nwd.GetTable<Customer>(); //
count = orders.Count();
sortCriteria = "";
var q = (from od in orders
join em in employees on od.EmployeeID equals em.EmployeeID
join ct in customers on od.CustomerID equals ct.CustomerID
//orderby em.EmployeeID
select new
{
od.OrderID,
od.ShipCountry,
ct.CompanyName,
ct.ContactName,
FullName = em.FirstName + ' '+ em.LastName,
}).ToList();
q.Dump();
sortCriteria: a string composed from sort and sortDir – in the format directly usable by Dynamic LINQ, e.g.
OrderID ASC
ContactName DESC, OrderID DESC
This is what you want to do:
if (sort == "OrderId") {
q = q.OrderBy(x => x.OrderId);
}
Passing in the the column name as a string is not a great way to do it though.