I know that a var is only in scope within it's method. But I've came across the situation where the collection 'var' from a database connection method, needs to be accessed in a subsequent Query() method in order to make a query.
The specific error is: The name collection doesn't exist in the current context
I've been referencing the MongoDB C# driver docs in order to set up the connection and the query and all seems correct besides this issue.
Does anyone know how I can restructure my code to resolve the error?
My two methods are specified as follows in an OrderRespository class, that makes database connections and queries:
//Method to create MongoDB Orders connection and get handle on collections
public static bool CreateConnection()
{
var client = new MongoClient(connectionString);
try
{
var database = client.GetDatabase("orders");
//Get a handle on the customers collection:
var collection = database.GetCollection<BsonDocument>("customers");
}
catch(MongoConnectionException)
{
return false;
}
return true;
}
//Method to test query on database documents
public async static Task<List<Customer>> FindCustomers()
{
var documents = await collection.Find(new BsonDocument()).ToListAsync();
List<Customer> customerList = await documents.ToListAsync();
return await documents.ToListAsync();
}
And this is the customer Model POCO class that models the collection fields:
public class Customer
{
/// <summary>
/// This attribute is used to map the Id property to the ObjectId in the collection
/// </summary>
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("firstName")]
public string firstName { get; set; }
[BsonElement("lastName")]
public string lastName { get; set; }
[BsonElement("email")]
public string Email { get; set; }
}
CreateConnection should return the collection that it's creating so that the person creating the connection can actually use it:
//Consider renaming this method; you're really here to get the customers,
//not create a connection
public static YourCollectionType<BsonDocument> CreateConnection()
{
var client = new MongoClient(connectionString);
var database = client.GetDatabase("orders");
//Get a handle on the customers collection:
return database.GetCollection<BsonDocument>("customers");
}
FindCustomers can then accept the collection as a parameter:
public async static Task<List<Customer>> FindCustomers(
YourCollectionType<BsonDocument> collection)
{
var documents = await collection.Find(new BsonDocument()).ToListAsync();
List<Customer> customerList = await documents.ToListAsync();
return await documents.ToListAsync();
}
You can then use CreateConnection to create the documents that you search through:
var customers = FindCustomers(CreateConnection());
If FindCustomers is something that would only ever make sense to use with a collection created by CreateConnection and you won't ever use the created object for anything else, then you could have FindCustomer call CreateConnection directly, but odds are those conditions won't apply.
Related
I got an error related with security when I tried to deserialize by using `System.Text.Json JsonSerializer`.
What do I want to achieve?
I want to give the user controle to transalte some records in my database, so use can follow this scenario:
1- User can choose model of my class library.
2- After selecting a class, user will select a property(filed) from this class.
3- User will get list of values of the selected property up.
4- Last step is not here right now, user can edit a certian value.
This my piece of code:
MyPage.razor.cs:
[Inject]
private IGenericHttpClient<Type> HttpClient { get; set; }
private Type SelectedType { get; set; }
// First select a class [Class library] from HTML Select
private void OnTypeChnage(ChangeEventArgs args)
{
string FullName = "My.Models." + args.Value.ToString();
// Create type of selected class
SelectedType = Assemble.GetType(FullName, false);
}
//Call api to get all fields of this class
private async Task OnPropertChange(ChangeEventArgs args)
{
var list = await
HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
}
GenericHttpClient.cs
public async ValueTask<List<T>> GetJsonAsync(string url)
{
using HttpResponseMessage response = await _client.GetAsync(url);
ValidateResponse(response);
var conetnt = await response.Content.ReadAsStringAsync();
//I got the error down
return JsonSerializer.Deserialize<List<T>>(conetnt, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
System.Text.Json does not support Type class due to security reasons. You send the full assembly name as a string and again try to construct the Type at the client end.
public async ValueTask<List<T>> GetJsonAsync(string url) this wont even compile, due to not specify generic information on method signature.
And also, your problem would come from the content of http response, otherwise, the Deserialize step should work fine.
I copied your code and make a small block that prove it.
// Define somewhere
public class GenericHttpClient
{
public List<T> GetJsonAsync<T>()
{
var content = "[{\"TestProp\": \"This is some test\"}]";
return JsonSerializer.Deserialize<List<T>>(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
}
public class Test
{
public string TestProp { get; set; }
}
// Test it
var test = new GenericHttpClient();
var result = test.GetJsonAsync<Test>();
Like what #Mayur Ekbote mentioned up, "System.Text.Json does not support Type class due to security reasons." I will add a solution but I don't think this solution is very efficient.
Change Type to Dynamic:
[Inject]
private IGenericHttpClient<dynamic> HttpClient { get; set; }
Use JsonElement to get the value as a string:
private async Task OnPropertChange(ChangeEventArgs args)
{
var langCode = CultureInfo.CurrentCulture.Name;
PropertyValueList.Clear();
var list = await HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
List<object> listValue = new List<object>();
SelectedProperty = args.Value.ToString();
string fieldName = char.ToLower(SelectedProperty[0]) + SelectedProperty.Substring(1);
foreach (var item in list)
{
//Convert object to JsonElement
var val = ((JsonElement)item).GetProperty(fieldName).GetString();
PropertyValueList.Add(val);
}
}
Why is it not efficient?
Because I got a list of value String instead of list of selected class.
I'm new to NoSql and MongoDB. I'm using the MongoDB C# driver inside Visual Studio.
I've read in different places that it's preferable to have a single instance of your database class that maintains the connection(s) to keep everything thread safe and that it's generally a bad idea to use static classes for database CRUD operations.
At the start of my program I instantiate my database class which opens a connection. Within that class and also in derived classes I can perform CRUD operations. But now I'm in a different part of my solution (same namespace, different class) and I need to do read operations to check if a user exists. I also need to compose a new document that I then want to insert.
Now I'm in a situation where that's only possible by creating a new instance of the database class to access its CRUD methods. I want to avoid static CRUD methods (that could be accessed from other classes) because then the base class of my database connection also needs to be static. I cannot figure out how to approach this and what would be the recommended way.
From the MongoDB website:
The MongoClient instance actually represents a pool of connections to the database; you will only need one instance of class MongoClient even with multiple threads.
http://mongodb.github.io/mongo-csharp-driver/2.2/getting_started/quick_tour/
Does this mean I should create a new MongoClient everytime I need to acces the database in others parts of my program?
UPDATE
It seems I was a bit mistaken about the static properties and how they can be used. I now have it setup like this:
class Database
{
const string MongoConnection = "mongodb+srv://user:password#cluster.mongodb.net";
public static MongoClient Client { get; set; }
public static IMongoDatabase Directory { get; set; }
public static IMongoCollection<User> Collection { get; set; }
public Database()
{
Client = new MongoClient(MongoConnection);
Directory= Client.GetDatabase("studentDB");
Collection = Directory.GetCollection<User>("users");
}
public static void InsertNewUser(User user)
{
Collection.InsertOne(user);
}
public static bool EmailHasAccount(string email)
{
return Collection.Find(x => x.Email == email).FirstOrDefault() == null ? false : true;
}
public static User RetrieveUserAccount(string email)
{
return Collection.Find(x => x.Email == email).FirstOrDefault();
}
}
public class User
{
public Guid Id { get; private set; }
public string Name { get; set; }
public string Email { get; set; }
public User(string name, string email)
{
Id = Guid.NewGuid();
Name = name;
Email = email;
}
}
And in my main program I can use it like this:
var db = new Database();
var user = new User("myName", "email#address");
Database.InsertNewUser(user);
Console.WriteLine(Database.EmailHasAccount("email#address")); // returns true
Console.WriteLine(Database.RetrieveUserAccount("email#address").Name); // returns "myName"
That's exactly what I was looking for. What would be the best way to handle multiple collections? Would it be safe to change the Collection property or is it better to create separate properties? Is a Generic even possible?
Hi there to the good friends of SO!
This is more of a design question so I'll get into a detailed example.
Let me explain the way we're sending emails.
In various parts of the application, we create entries in our Notification table for different kinds of email we might have to send.
For eg: The NotificationQueue table looks like this:
NotificationQueueID OrderID EmailType Notes SentDatetime
1 461196 OrderUpdate SomeNote1 2020-09-01 14:45:13.153
2 461194 OrderCancellation SomeNote2 2020-09-01 14:45:13.153
It's accessed using the property in the DbContext as:
public DbSet<NotificationQueue> NotificationQueues { get; set; }
The different types of email is modeled in an enum:
public enum TypeOfEmail
{
OrderCancellation,
OrderUpdate
}
We have a EmailModel class that has a TicketsInNotificationQueue property that has a list of any of the email types we have. For eg: At any given time, it can have list of either UpdatedTickets or CancelledTickets. The email type says what type of tickets are in the TicketsInNotificationQueue property.
public class EmailModel
{
public EmailModel(TypeOfEmail emailType, TicketsInNotificationQueue ticketsInNotificationQueue)
{
EmailType = emailType;
TicketsInNotificationQueue = ticketsInNotificationQueue;
}
public TypeOfEmail EmailType { get; set; }
public TicketsInNotificationQueue TicketsInNotificationQueue { get; set; }
}
public class TicketsInNotificationQueue
{
public List<OrderCancellation> CancelledTickets { get; set; }
public List<OrderUpdate> UpdatedTickets { get; set; }
}
public class OrderCancellation : CommonOrderInformation
{
public string SomeOrderId { get; set; }
}
public class OrderUpdate: CommonOrderInformation
{
public string SomeUpdateRelatedProperty { get; set; }
}
public class CommonOrderInformation
{
public int NotificationQueueId { get; set; }
public string ReferenceNumber { get; set; }
}
There's a method that retrieves tickets from Notification table:
public async Task<TicketsInNotificationQueue> GetTicketsfromNotificationQueueAsync(TypeOfEmail emailType)
{
var ticketsInNotificationQueue = new TicketsInNotificationQueue();
using (var dbCon = GetSomeDbContext())
{
var notifications = dbCon.NotificationQueues.Where(x => x.EmailType == emailType.ToString()).ToList();
foreach (var ntf in notifications)
{
if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())
{
if (ticketsInNotificationQueue.CancelledTickets == null)
{
ticketsInNotificationQueue.CancelledTickets = new List<OrderCancellation>();
}
ticketsInNotificationQueue.CancelledTickets.Add(new OrderCancellation()
{
NotificationQueueId = ntf.NotificationQueueID,
ReferenceNumber = ntf.OrderID,
SomeOrderId = "Something from a table."
});
}
else if (ntf.EmailType == TypeOfEmail.OrderUpdate.ToString())
{
if (ticketsInNotificationQueue.UpdatedTickets == null)
{
ticketsInNotificationQueue.UpdatedTickets = new List<OrderUpdate>();
}
var notes = dbCon.NotificationQueues.FirstOrDefault(x => x.NotificationQueueID == ntf.NotificationQueueID)?.Notes;
ticketsInNotificationQueue.UpdatedTickets.Add(new OrderUpdate()
{
NotificationQueueId = ntf.NotificationQueueID,
ReferenceNumber = ntf.OrderID,
SomeUpdateRelatedProperty = "Something from a table."
});
}
}
}
return ticketsInNotificationQueue;
}
Now I just take this list, and filter out the notificationIds for the type of tickets that I just received, and work on them down the line. (I need those notificationIds to set the SentDatetime after the notification has been sent).
var ticketsReceived = false;
notificationIds = new List<int>();
if (ticketsInNotificationQueue.CancelledTickets != null && ticketsInNotificationQueue.CancelledTickets.Any())
{
ticketsReceived = true;
notificationIds = ticketsInNotificationQueue.CancelledTickets.Select(x => x.NotificationQueueId).ToList();
}
else if (ticketsInNotificationQueue.UpdatedTickets != null && ticketsInNotificationQueue.UpdatedTickets.Any())
{
ticketsReceived = true;
notificationIds = ticketsInNotificationQueue.UpdatedTickets.Select(x => x.NotificationQueueId).ToList();
}
if (ticketsReceived)
{
// Proceed with the process of sending the email, and setting the `SentDateTime`
}
The problem I see here is that as the type of emails grows bigger, let's say 10-20, the method to retrieve tickets and filter them out later needs to grow so big that it's going to spin out of control in terms of readability and code manageability which I'm not liking at all. The part where I need to check what emailType is requested in the fetch and what emailType has been received(to get the corresponding notificationIds for SentDateTime update).
So is there some other way to design this workflow (I'm even open to using reflection and such) to make it more manageable and concise?
Any help would be greatly appreciated!
There is significant improvements that you can make to the existing system and the existing code. In the interest of having a more complete answer I'm going to recommend a not-too-expensive system overhaul and then proceed to your exact answer.
A different and industry standard approach
You already have the data structure correct, this is a perfect job for distributed persistent queues, where you don't need to worry about querying the database as much; instead you just enqueue the messages and have a processor that deals with them. Since you're using C# and .net, I strongly encourage you to check out Azure Service Bus. This is effectively a large queue where you can send messages (in your case send email requests) and you can enqueue your messages to different channels in the service bus depending on their type.
You could also look into creating a queue processor / which Azure Functions have a trigger out of the box. Once your email is sent, then you can write to your DB, we've sent this email.
So, the good design looks like
Have distributed persistent queues, channels / enqueue the email requests to them directly.
If you want to process them at a cadence, run your processor using cron - which most industry solutions support.
If you want to process them as they are ending up in the queue, use a trigger.
You can enrich your processor based on your scenario, it looks like it has something to do with orders, so you may need to handle cases like not sending an already queued email after an order in cancelled, etc..
Improving what you have
Due to some circumstances, the solution above might not be available to you - so let's get to it.
See how to refactor switch statements (since you have one with if / else ifs)
https://sourcemaking.com/refactoring/smells/switch-statements
Ways to eliminate switch in code
You could get this through polymorphism, just create a base mail type and override the behaviors in subclasses. This way you can associate the correct queue with the correct email type.
Example:
var results = await getSomeEmails(OrderMail);
// returns a separate processor inherited from the base one, implemented in different ways.
var processor = ProcessorFactory.Create(OrderMail);
await processor.Send(results);
Some more improvements
foreach (var ntf in notifications)
{
if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())
You are checking the email type over and over again unnecessarily in this loop, you should look into moving those statements above the for and check through the passed-in parameter, since you already know the type you're querying for.
Thank you for the answer #Mavi Domates.
But this is what I ended up doing:
I modified the EmailModel's TicketsInNotificationQueue property so that instead of having different types of classes for different types of email, we just have one type of common class. This will avoid having us to put those checks for checking what kind of email was requested in the fetch logic and also to retrieve notification Ids down the line (to update SentDateTime after email is sent) as indicated in the original question.
public class EmailModel
{
public EmailModel(TypeOfEmail emailType, IEnumerable<CommonEmailModel> ticketsInNotificationQueue)
{
EmailType = emailType;
TicketsInNotificationQueue = ticketsInNotificationQueue;
}
public TypeOfEmail EmailType { get; set; }
public IEnumerable<CommonEmailModel> TicketsInNotificationQueue { get; set; }
}
public enum TypeOfEmail
{
OrderCancellation,
OrderUpdate
}
I added a new class called: CommonEmailModel and removed all those different email type classes (classes for OrderCancellation, OrderUpdate etc.).
public class CommonEmailModel
{
// Common to all email types. A lot of email types only need these first 4 properties
public string EmailType { get; set; }
public int NotificationQueueId { get; set; }
public string OrderId { get; set; }
public string Notes { get; set; }
// Cancellation related
public string SomeOrderId { get; set; }
// Update related
public string SomeUpdateRelatedProperty { get; set; }
public static async Task<IEnumerable<CommonEmailModel>> GetEmailBodyRecordsAsync(TypeOfEmail emailType)
{
var emailModels = new List<CommonEmailModel>();
var emailEntries = await EmailNotificationQueue.GetEmailEntriesAsync(emailType);
var relevantOrdIds = emailEntries.Select(x => x.OrderID).Distinct().ToList();
using (var dbCon = GetSomeDbContext())
{
orders = dbCon.Orders.Where(x => relevantOrdIds.Contains(x.OrdNumber)).ToList();
}
foreach (var record in emailEntries)
{
var emailModel = new CommonEmailModel
{
EmailType = emailType,
NotificationQueueId = record.NotificationQueueID,
OrderId = record.OrderID,
Notes = record.Notes,
SomeOrderId = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.SomeOrderIdINeed,
SomeUpdateRelatedProperty = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.UpdateRelatedPropertyINeed
};
emailModels.Add(emailModel);
}
return emailModels;
}
}
I just get the records the following way:
var emailRecords = await CommonEmailModel.GetEmailBodyRecordsAsync(emailType);
And simply pass this to EmailModel constructor as the ticketsInNotificationQueue parameter. No need to do all that extra check of figuring out if records of certain emailType was requested. The views for OrderCancellation and OrderUpdate will use the common properties and their respective relevant properties that are present in the CommonEmailModel class.
if (emailRecords.Any())
{
var emailModel = new EmailModel(emailType, emailRecords);
}
Now all I have to do is pass the notification Ids to a method that marks the SentDateTime column with the current timestamp by simply calling:
if (emailWasSent)
{
await UpdateNotificationSentTimeAsync(emailRecords.Select(t => t.NotificationQueueId));
}
In the future if we keep on adding new emailType (most probably they'll carry the information in those 4 first common properties in CommonEmailModel), we can simply add new properties to the CommonEmailModel to accommodate that and just create a new view. This way I can avoid code repetition and complexity in the fetch and also at the end while updating the SentDateTime.
I have some code I am trying to convert. I don't have these ObjectResult and ObjectContext anymore
This is what I did have:
public virtual ObjectResult<string> GetTransData(string iN_MEM_ID)
{
var iN_MEM_IDParameter = iN_MEM_ID != null ?
new ObjectParameter("IN_MEM_ID", iN_MEM_ID) :
new ObjectParameter("IN_MEM_ID", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<string>("GetTransData", iN_MEM_IDParameter);
}
Since I need a List to be returned from a caller ( it is sent back as json data )
This is what I am trying to build
public virtual List<string> GetTransData(string iN_MEM_ID)
{
var iN_MEM_IDParameter = iN_MEM_ID != null ?
new SqlParameter("IN_MEM_ID", iN_MEM_ID) :
new SqlParameter("IN_MEM_ID", typeof(string));
Clinical_CaseTrakker_Context clinical = new Clinical_CaseTrakker_Context();
List<string> offLine = clinical.string.FromSql("EXECUTE CT.GetTransData {0}", iN_MEM_IDParameter);
return offLine;
}
Notice that I am stuck with clinical.string i can't do that , but I am not sure how to take dbcontext instance and run FromSql to execute sql and return to List
In EF Core, it is not possible to use the FromSql method to return a subset of properties (a projection) directly from the database.
You are required to define a some model and a DbSet for that class
public class Foo
{
public string Bar { get; set; }
}
then declare in your context
public DbSet<Foo> Foos { get; set; }
and use it like:
using (var context = new Clinical_CaseTrakker_Context())
{
var offLine = context.Foos
.FromSql($"EXECUTE CT.GetTransData {iN_MEM_IDParameter}")
.Select(x => x.Bar)
.ToList();
return offLine;
}
Your Context needs a virtual DbSet<string> ResultStrings { get; set; } that you can call upon and put the result in. (This does not work, see this post or Roman Marusyk comment below)
EDIT: Your Context needs a virtual DbSet<ResultEntity> ResultEntities { get; set; } that you can call upon and put the result in.
Then you can do return clinical.ResultEntities.FromSql("EXECUTE CT.GetTransData {0}", iN_MEM_IDParameter").toList() to fill the set.
Considering the ResultEntity has an Id and a value property, you can do ResultEntities.Select(e => e.value).toList() to extract a list of strings from the set.
I have a requirement that wants me to replace a value (StatusName) that is a field (ChildItems) in a list that is a field in another list (ParentItems). I haven't worked a lot with Async / Await so I'm sure I'm not doing this correctly
Here is an example of what I'm trying:
public class parentItem {
public int Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public List<childItem> ChildItems {get;set;}
}
public class childItem {
public int Id {get;set;}
public string Name {get;set;}
public string StatusId {get;set;}
public string StatusName {get;set;}
}
// Populate parent in list.
List<parentItem> parents = new List<parentItem>();
//populate....
We have a data call that will populate these elements in an async await method. I was hoping to do a simple lambda expression that will call another method that will change the value of the status
Kind of like this:
// lambda I was hoping to try
parents = parents.Select((par) => {
par.ChildItems = par.ChildItems.Select((sub) => {
sub.StatusName = await ChangeChildStatusName(sub.StatusId);
}).ToList();
return par;
});
public async Task<string> ChangeChildStatusName(int statusId) {
// code.
}
In this method (ChangeChildStatusName) there is a database call getting other information that is needed. I'm pretty sure there is a different approach for something like this, but so far I haven't been able to find a solution that fits this scenario.
The key here is to pay attention to your types. The result of your inner Select (after you add a return sub;) is a List<Task<childItem>>, which you can use with await Task.WhenAll(..) to (asynchronously) wait for them all to complete. Similarly, your parent tasks can use the same technique:
var parentTasks = parents.Select(async (par) => {
var childTasks = par.ChildItems.Select(async (sub) => {
sub.StatusName = await ChangeChildStatusName(sub.StatusId);
return sub;
});
par.ChildItems = (await Task.WhenAll(childTasks)).ToList();
return par;
});
parents = (await Task.WhenAll(parentTasks)).ToList();
However, in your particular case (accessing a database), you can probably only do one database call at a time anyway, so you should replace your nested Selects with nested foreachs:
foreach (var par in parents)
foreach (var sub in par.ChildItems)
sub.StatusName = await ChangeChildStatusName(sub.StatusId);