I have the following classes:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public List<Event> Events { get; set; }
}
public class Event
{
public int Id { get; set; }
public int UserId { get; set; }
public string EventText { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public string Day { get; set; }
public string ColorIdentifier { get; set; }
public int Week { get; set; }
}
I'm trying to get all the users and their events with Dapper like this:
var sql = "SELECT u.Id, e.UserId, e.EventText FROM cpc.PLANNING_TOOL_USERS u LEFT JOIN cpc.PLANNING_TOOL_EVENTS e ON u.Id=e.UserId";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (u.Events == null)
u.Events = new List<Event>();
u.Events.Add(e);
return u;
}, splitOn: "Id, UserId");
The Id for the user is returned back, but the list of events is not populated. I have looked at many examples here on Stack Overflow regarding this, but I can't see what I'm doing wrong.
To omit the situation that SQL returns no data I have just mocked two user rows with SQL union.
User with Id=1 and one Event, and User with Id=2 and two Events.
SqlMapper.Query returns flat results that are best for 1 to 1 relation. You have one user to many events relation, so some helper storage needed to maintain that relation as a mapping thru the results. I have used .NET dictionary for that.
My code sample below:
// introducing temporary storage
var usersDictionary = new Dictionary<int, User>();
var sql = #"SELECT 1 Id, 1 UserId, 'EventText1' EventText
union SELECT 2 Id, 2 UserId, 'EventText2' EventText
union SELECT 2 Id, 2 UserId, 'Another EventText2' EventText";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (!usersDictionary.ContainsKey(u.Id))
usersDictionary.Add(u.Id, u);
var cachedUser = usersDictionary[u.Id];
if (cachedUser.Events == null)
cachedUser.Events = new List<Event>();
cachedUser.Events.Add(e);
return cachedUser;
}, splitOn: "UserId");
// we are not really interested in `result` here
// we are more interested in the `usersDictionary`
var users = usersDictionary.Values.AsList();
Assert.AreEqual(2, users.Count);
Assert.AreEqual(1, users[0].Id);
CollectionAssert.IsNotEmpty(users[0].Events);
Assert.AreEqual(1, users[0].Events.Count);
Assert.AreEqual("EventText1", users[0].Events[0].EventText);
Assert.AreEqual(2, users[1].Events.Count);
I hope that helped you solving your mapping issue and events being null.
Related
I have many to many relationship between entities user and group, I also have joining table GroupParticipants.
public class User
{
public string Id {get; set;}
public ICollection<GroupParticipant> Group { get; set;}
}
public class Group
{
public int Id { get; set; }
public ICollection<GroupParticipant> Participants { get; set; }
}
public class GroupParticipant
{
public int GroupId { get; set; }
public string ParticipantId { get; set; }
public User Participant { get; set; }
public Group Group { get; set; }
}
I need to select groups which user specified user did not join. I want to do something like:
string userId = 5;
var groupsAvailableToJoin = await _context.Groups
.Where(group => group.Participants.Id != userId);
Thanks!
A query like:
_context.Groups.Where(g =>
!_context.GroupParticipants.Any(gp => gp.UserId == userId && gp.GroupId == g.I'd
);
Should translate to:
SELECT * FROM Groups g
WHERE NOT EXISTS(SELECT null FROM groupParticipants gp WHERE gp.UserId = 5 AND gp.GroupId = g.Id)
Which should be a reasonably performant way of getting you what you're looking for.. I'm sure that the GroupParticipants columns are indexed..
There are various ways to write this - if you find a two step approach easier to understand, it's effectively the same as:
var joined = _context.GroupParticipants.Where(gp => gp.UserId == 5).Select(gp => gp.GroupId).ToList();
var notJoined = _context.Groups.Where(g => !joined.Contains(g.Id));
This one translates as a NOT IN (list,of,groups,they,are,in) for a similar effect
Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes
We have Customer transactional table with multiple lookup tables with foreign keys. We want to see the 3 table joined together.
How do I make sure Service call is conducted one? I don't want to grab All unnecessary customer transactions, and then filter in memory. I want to make one service call to find customer transaction > 50 in one sql query.
Repository:
CustomerTransaction GetTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).ToList()
return result;
}
Service Call:
void GetByCustomerTransactionGreaterthan50(int id)
{
var newdata = CustomerTransaction.GetByCustomerTransactionId();
nt.newdata.Where(x => x.PurchaseAmount > 50).ToList()
return newdata;
}
Models:
public class CustomerTransaction
{
public int CustomerTransactionId{ get; set; },
public int ProductTypeId {get; set; }, //joins to ProductTypeTable
public int StatusID {get; set; }, //joins to StatusTypeTable
public string DateOfPurchase{ get; set; },
public int PurchaseAmount { get; set; },
}
public class ProductType
{
public int ProductTypeId{ get; set; }
public string ProductName { get; set; },
public string ProductDescription { get; set; },
}
public class StatusType
{
public int StatusId{ get; set; }
public string StatusName{ get; set; },
public string Description{ get; set; },
}
You can make a Select projection before the ToList() this will make sure that your sql statement that EF generate include just fields you are projecting too.
Something like:
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).Select(x=> new CustomerTransaction()).ToList()
and to filter you can include your filter in the where condition:
ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > 50)
Or make your GetTransactionbyCustomerId return a Query and filter as you did in the service call
Option 1
You will need to adjust your repository in order to be able to add to your query in Service Layer:
Repository:
IQueryable<CustomerTransaction> QueryTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid);
return result;
}
Option 2
Or to create another method in your data access layer:
Repository:
List<CustomerTransaction> GetTransactionByCustomerIdAndAmount(int customerid, int amount)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > amount).ToList()
return result;
}
Based on your architecture, you should pick one of these options.
Im trying to use Dapper in an ASP.Net Core application to map multiple tables to one object with other objects as its properties.
My tables are as follows (just basic summary):
user table
address table (no user ids stored in this table, this table is just address records)
address_type table (lookup table)
phone_number table (no user ids stored in this table, this table is
just phone records)
phone_number _type table (lookup table)
user_has_address table - this table has a user_id, address_id and address_type_id
user_has_phone_number table - this table has a
user_id, phone_number _id and phone_number _type_id
Basically whats happening is results get returned fine if all the users have no address or phone records or if only the first user in the list has address/phone records. What I want is if a user has an address/phone number then that dictionary is populated, otherwise I still want all the user info but that address/phone number dictionary will be empty.
My objects look like the following:
public class User
{
public uint id { get; set; }
public DateTime modified_date { get; set; }
public uint modified_by { get; set; }
public string user_name { get; set; }
public uint company_code { get; set; }
public string email { get; set; }
public bool active { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public Dictionary<uint, Address.Address> addresses { get; set; }
public Dictionary<uint, Address.PhoneNumber> phone_numbers { get; set; }
}
public class Address
{
public uint address_id { get; set; }
public AddressType address_type { get; set; }
public string address_line1 { get; set; }
public string address_line2 { get; set; }
public string address_line3 { get; set; }
public string city { get; set; }
public string state { get; set; }
public string country_code { get; set; }
public string postal_code { get; set; }
public sbyte is_po_box { get; set; }
}
public class AddressType
{
public uint id { get; set; }
public string name { get; set; }
}
public class PhoneNumber
{
public uint id { get; set; }
public PhoneNumberType phone_number_type { get; set; }
public string phone_number { get; set; }
public string phone_ext { get; set; }
}
public class PhoneNumberType
{
public uint id { get; set; }
public string name { get; set; }
}
Here is my function where I try to use Dapper to map to the User class:
public List<User> GetUsersByStatus(uint companyCode, string status)
{
if (companyCode == 0)
throw new ArgumentOutOfRangeException("companyID", "The Company ID cannot be 0.");
List<User> Users = new List<User>();
try
{
string sql = #"SELECT u.*, ad.*, adt.*, p.*, pt.*
FROM master.user u
LEFT JOIN master.user_has_address AS uha ON uha.user_id = u.id
LEFT JOIN master.address AS ad ON ad.id = uha.address_id
LEFT JOIN master.lookup_address_type adt ON adt.id = uha.address_type_id
LEFT JOIN master.user_has_phone_number AS uhp ON uhp.user_id = u.id
LEFT JOIN master.phone_number AS p ON p.id = uhp.phone_number_id
LEFT JOIN master.lookup_phone_number_type pt ON pt.id = uhp.phone_number_type_id
WHERE u.company_code = " + companyCode;
switch (status)
{
case "1":
// Active Status.
sql = sql + " AND (u.active = TRUE)";
break;
case "2":
// Retired Status.
sql = sql + " AND (u.active = FALSE)";
break;
}
sql = sql + " ORDER BY u.user_name";
using (var conn = new MySqlConnection(connectionString))
{
conn.Open();
var userDictionary = new Dictionary<uint, User>();
conn.Query<User, Address, AddressType, PhoneNumber, PhoneNumberType, User>(sql, (u, ad, adt, p, pt) =>
{
User user;
if (!userDictionary.TryGetValue(u.id, out user))
userDictionary.Add(u.id, user = u);
if (ad != null && adt != null)
{
Address address = ad;
address.address_type = new AddressType() { id = adt.id, name = adt.name };
if (user.addresses == null)
user.addresses = new Dictionary<uint, Address>();
if (!user.addresses.ContainsKey(adt.id))
user.addresses.Add(adt.id, address);
}
if (p != null && pt != null)
{
PhoneNumber phone = p;
phone.phone_number_type = new PhoneNumberType() { id = pt.id, name = pt.name };
if (user.phone_numbers == null)
user.phone_numbers = new Dictionary<uint, PhoneNumber>();
if (!user.phone_numbers.ContainsKey(pt.id))
user.phone_numbers.Add(pt.id, phone);
}
return user;
},
splitOn: "id,id,id,id").AsQueryable();
Users = userDictionary.Values.ToList();
}
}
catch(Exception ex)
{
//TO DO: log exception
}
return Users;
}
I've tried tracing through it and in cases where the 3rd user (for example) has address/phone records, it seems to grab the first 2 users fine but then jump right out of the query portion when it gets to the record with address/phone numbers and then it returns an empty Users list.
Does anyone have any idea what Im doing wrong?
I'm not sure this is the solution, but I have a problem with this code:
User user;
if (!userDictionary.TryGetValue(u.id, out user))
userDictionary.Add(u.id, user = u);
Remember that u is parameter of the lambda, and will be changed during execution. I would do this:
User user;
if (!userDictionary.TryGetValue(u.id, out user))
{
user = new User(u); // Make a new instance of user
userDictionary.Add(u.id, user);
}
Also you should definitely use parameters for your query:
WHERE u.company_code = #CompanyCode";
And finally I don't think it should be the responsibility of this code to construct dictionaries for holding addresses and phone numbers. The User constructor should take care of that.
Unfortunately, I must have 50 rep to add a comment, so I'll make this an answer.
Are all the fields in the DB concerning phone and address non-nullable? If no, then the mistake could be that it can't match them with your C# classes. In this case, declare immutable types nullable so that they match DB types. Like this:
int? val;
Turns out the main issue was the fact that I left the address_id parameter in the Address class named the way it was, it should ahve been just 'id'. I updated that and it works now, I did also update my code according to Palle Due's suggestion so that may have contributed as well.
I have the following query:
IQueryable<BarcodeQuery> barcodes = db.Barcodes.Select(b => new BarcodeQuery
{
id = b.id,
category_id = b.category_id,
...
checkout = b.Checkouts.Select(c => new CheckoutChild
{
id = c.id,
loanee_id = c.loanee_id,
...
})
.Where(c => c.datein == null)
.FirstOrDefault()
});
And so on. It's based on this model:
public class BarcodeQuery
{
public int id { get; set; }
public int category_id { get; set; }
...
public CheckoutChild checkout { get; set; }
public CheckoutStatus checkoutStatus { get; set; }
}
My question is about CheckoutStatus down there at the bottom. It looks like this:
public class CheckoutStatus
{
public string status { get; set; }
public int daysUntilDue { get; set; }
public int daysOverdue { get; set; }
}
All of those values are derived from information I get from the query--none of them are in the database itself. What is the best way of inserting the CheckoutStatus values into each barcode record?
I have a function that creates the CheckoutStatus values themselves, I just don't know how to get them into the barcode records.
Thanks!
If b has just be created with new, how can b.Checkouts contain something? I do not really understadn what you are trying to do.
EF is converting the lambda expression into a SQL statement. Therefore you can only use expressions that can actually be translated to SQL. Just query the barcodes from the DB and then add the missing information to the barcodes returned in a loop.
var barcodes = db.Barcodes.Select(...).ToList();
foreach (Barcode b in barcodes) {
b.Checkouts = ...
}