LINQ to SQL - Format a string before saving? - c#

I'm trying to convert an existing (non-LINQ to SQL) class into a LINQ to SQL entity class which has an existing (db column) property like:
public string MyString
{
get { return myString; }
set { myString = FormatMyString(value); }
}
Is there a way to do this sort of processing on the value of entity class property before saving?
Should I use some sort of an entity-level saving event in which to do my formatting (if that will even work)?
I know LINQ to SQL provides validation and there are generated On...Changing() partial methods which provide access to the new value, by value (not by ref), but none of those methods seem to provide a way to actually modify/format the value while it is being set.
Thank you for your help.

What about using On...Changed()? It fires after the property value has changed. There you can check its value and update it using the FormatString.

Related

Checking Property value: Data validation using Data Annotations - .NET

I am wanting to validate that a property value is set to a specific text. I came across [validation] (https://www.tutorialsteacher.com/mvc/implement-validation-in-asp.net-mvc) and saw how there are many options such as Required which is very helpful. But I do not see one to ensure that a property has a specific value. Is there a way I can do that using data annotations? Is regular expression for that?
i.e.
ClassName property must be set to "science". Is there a data annotation for property values? Is regular expression for that?
public class Student{
[DataAnnotation("science")]
public string ClassName{ get; set; }
}
I have searched over a few articles and still cannot find the solution to my problem. Would like to know if this is possible with data annotations?
You need to Use CustomValidationAttribute. Please refer below
https://learn.microsoft.com/en-us/previous-versions/aspnet/cc668224(v=vs.100)
https://www.codeproject.com/Tips/5257153/How-to-Create-Custom-Validation-Attribute-in-Cshar
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.customvalidationattribute?view=netframework-4.8
Basically, these attributes will have Isvalid function. In you case you can put the validation as Property value == science

C# SqlCommand returns 0

I've created a SQL query that I execute with the following command, and it returns the correct number of entries but these contains all 0:
If I run the same SQL command in my Management Studio, it works correctly.
I also tried it with a Linq statement and it works also correctly:
I hope you guys can help me to solve the problem.
You shouldn't be projecting into a List<T> - the ToList() does that. Basically, simplify:
var data = dbContext.Database.SqlQuery<Tuple<DateTime, string, string>>(...).ToList();
It might also work with value-tuples:
var data = dbContext.Database.SqlQuery<(DateTime, string, string)>(...).ToList();
which would also allow you to conceptually name them:
var data = dbContext.Database.SqlQuery<(DateTime Datum, string Text, string Bemerkung)>
(...).ToList();
Note: concatenating filter is almost certainly a SQL injection vulnerability; if looks like you should be using a SQL parameter there instead.
#Evk notes that EF might not support column-wise binding of tuples. If that is the case, then your best bet would be to create a POCO that matches the column definitions:
class Foo // rename me to something meaningful
{
// note: there may be custom attributes you can use
// to make these names less ugly, i.e.
// [Column("TEXT")] on a property called Text
public DateTime RMA_DATUM {get;set;}
public string TEXT {get;set;}
public string BEMERKUNG {get;set;}
}
and use SqlQuery<Foo>.

How do I retrieve a CimInstance property of a certain type?

So, I have been searching for a while now, and I can't seem to find the answer to what seems to be (at least, to me) a simple, foundational C# question.
To set it up, I am putting together a C# GUI program that leverages CIM classes from remote machines to retrieve data about said machine, which will be used to determine it's current "Status" from an End-User Support Technician's point of view.
I have had no problems with setting up the CIM session to the remote machine, and retrieving the data I requested by querying for instances. The problem I have run into is that I can't seem to figure out how to retrieve a value from a property from one of these instances with the value returned being of a certain expected type. Without actually running the query before the code is compiled and executed, the IDE (Visual Studio 2017) assumes the return value to be of the super-type "Object" (Please forgive any vernacular problems).
Here is the Code I am using for reference:
public class DispRecord
{
//declare properties for record object
public string Hostname;
public string Status;
public string User;
public string NTLogin;
public string LockTime;
public string LockDuration;
public string LogonTime;
public string LogonDuration;
public string LastRestart;
public string PwrOnDuration;
}
I have a constructed custom class object that contains the properties I am retrieving and calculating.
This custom object is then passed to a function that is performing the query and assigning the value to the properties. My method class has several methods, but here is the one that currently is the sticking point:
//method for gathering CIM data
public static DispRecord QueryCIMData (DispRecord Record)
{
//use cimsession to remote host
using (CimSession Session = CimSession.Create(Record.Hostname, new DComSessionOptions()))
{
//declare queries
string PwrQuery = "Select LastBootUpTime from CIM_OperatingSystem";
//string ProcQuery = "Select CreationDate,Caption from CIM_Process where Name='explorer.exe' or Name='logonui.exe'";
//declare namespace
string Namespace = #"root\cimv2";
//perform PwrQuery and retrieve lastbootuptime
IEnumerable<CimInstance> Results = Session.QueryInstances(Namespace, "WQL", PwrQuery);
DateTime LastBootUpTime = DateTime.Parse(Results.First().CimInstanceProperties["LastBootUpTime"].Value.ToString());
//add PwrOnDuration and LastRestart to Record
Record.LastRestart = LastBootUpTime.ToString();
Record.PwrOnDuration = (DateTime.Now - LastBootUpTime).ToString(#"dd\/hh\:mm\:ss");
}
//return changed record object
return Record;
}
What you see above does work, but I feel like the code gyrations necessary to achieve the proper output are a bit ridiculous and I feel that there must be another, possibly easier or cleaner, way to achieve my desired output. Surely there is a better way to retrieve the DateTime object I am expecting than to retrieve a property value as a string constructed from a DateTime object, that is then parsed into a new Datetime object, especially considering that I am turning around and converting it back into a string to insert into the record.
Ideally, I would like to do something like this, but I'm not sure how to achieve it:
DateTime LastBootUpTime = Results.First().CimInstanceProperties["LastBootUpTime"].Value;
When I attempt the above, the Compiler will throw an exception stating that it cannot implicitly convert a value of type 'Object' to type 'DateTime'
Essentially, since the query isn't performed until runtime, the returned CIM instance properties are all just expected as Objects, instead of their expected output based on the class that the instance is built from (Which in this case, a 'DateTime' object is expected as the value of the "LastBootUpTime" property).
The Code doesn't know what is coming out of the egg before it hatches, even if MSDN does.
Can anyone help me with this seeming easy problem?
Okay, after some trial and error, I found the easy solution I was sure existed.
To work with the compiler, a call to the Convert.ToDateTime() method allowed the code to compile and no exception was thrown at run-time.
//perform PwrQuery and retrieve lastbootuptime
IEnumerable<CimInstance> Results = Session.QueryInstances(Namespace, "WQL", PwrQuery);
DateTime LastBootUpTime = Convert.ToDateTime(Results.First().CimInstanceProperties["LastBootUpTime"].Value);
//add PwrOnDuration and LastRestart to Record
Record.LastRestart = LastBootUpTime.ToString();
Record.PwrOnDuration = (DateTime.Now - LastBootUpTime).ToString(#"dd\/hh\:mm\:ss");
The only cost was a few "DOH!"s :)

Mapping Enum to string column with custom SqlMapper.ITypeHandler - Dapper

I have a large number of PL/SQL stored procs that return columns with single character strings representing some kind of status value from a fixed range. In the project I'm working on, these columns have been mapped by Dapper to string properties on the domain objects, which are awkward and unreliable to manage, so I'd like to switch to enums.
If I used enums with single character names like enum Foo {A, P} I'm pretty sure Dapper would map them correctly but I don't want that, I want enums with descriptive labels like so:
enum Foo {
[StringValue("A")]
Active,
[StringValue("P")]
Proposed
}
In the above example, StringValueAttribute is a custom attribute and I can use reflection to convert the "A" to Foo.Active, which works fine - except I need Dapper to perform that conversion logic for me. I wrote a custom type handler to do this:
public class EnumTypeHandler<T> : SqlMapper.TypeHandler<T>
{
public override T Parse(object value)
{
if (value == null || value is DBNull) { return default(T); }
return EnumHelper.FromStringValue<T>(value.ToString());
}
public override void SetValue(IDbDataParameter parameter, T value)
{
parameter.DbType = DbType.String;
parameter.Value = EnumHelper.GetStringValue(value as Enum);
}
}
//Usage:
SqlMapper.AddTypeHandler(typeof(Foo),
(SqlMapper.ITypeHandler)Activator.CreateInstance(typeof(EnumTypeHandler<>).MakeGenericType(typeof(Foo)));
The registration with SqlMapper.AddTypeHandler() seems to work fine, but when my DbConnection.Query() code runs, I get an error saying that the value 'A' could not be converted - the error is thrown from Enum.Parse, suggesting that Dapper isn't actually calling my type handler at all despite it being registered. Does anyone know a way around this?
Another user has reported this as an issue on Dapper's github site. Seems like it's a deliberate optimisation specifically around enums in Dapper, so I've changed my database model rather than trying to change the mapping code. I looked at trying to modify Dapper itself, but the source code of Dapper is optimised like nothing I've ever seen, emitting opcodes to perform conversions in the most performant way possible - no way I want to start trying to work out how to make changes there.

how to iterate through reader for notnull without repeating code over and over

First off, I am new to programming (especially with C#) and thanks for your help.
I have a static web form with about 150 form objects (most checkboxes). I decided to go 1 record per form submission in the sql db. So, for example, question X has a choice of 5 checkboxes. Each of these 5 checkboxes has a column in the db.
I have the post page complete(working) and am building an edit page where I load the record and then populate the form.
How I am doing this is by passing a stored proc the id and then putting all the returned column values into the according object properties, then setting the asp control object to them.
An example of setting the asp controls to the selected value:
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
As you see, this is very tiresome since there are over 150 of these to do. Also, I just found out if the response is NULL then I get the error that it cant be converted to a string. So, I have added this line of code to get past it:
This is the part where I am populating the returned column values into the object properties (entity is the object):
if (!String.IsNullOrEmpty((string)reader["questionX0"].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX0"];}
So, instead of having to add this if then statement 150+ times. There must be a way to do this more efficiently.
First of all, it seems that you are using string.IsNullOrEmpty(value), but this won’t check for the special DBNull value that is returned from databases when the data is null. You should use something more akin to value is DBNull.
The rest of your problem sounds complex, so please don’t be put off if my answer is complex too. Personally I would use custom attributes:
Declare a custom attribute
The following is a skeleton to give you the idea. You may want to use the “Attribute” code snippet in Visual Studio to find out more about how to declare these.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class QuestionColumnAttribute : Attribute
{
public string ColumnName { get; private set; }
public QuestionColumnAttribute(string columnName)
{
ColumnName = columnName;
}
}
Use the custom attribute in the entity class
Where you declare your entity class, add this custom attribute to every field, for example where patientUnderMdTreatment is declared:
[QuestionColumn("questionX0")]
public string patientUnderMdTreatment;
Iterate over the fields
Instead of iterating over the columns in the reader, iterate over the fields. For each field that has a QuestionColumnAttribute on it, get the relevant column from the reader:
foreach (var field in entity.GetType().GetFields())
{
var attributes = field.GetCustomAttributes(typeof(QuestionColumnAttribute), true);
if (attributes.Length == 0)
continue;
object value = reader[attributes[0].ColumnName];
if (!(value is DBNull))
field.SetValue(entity, value.ToString());
}
For the first part of your question where you set the ASP controls, you can use a similar strategy iterating over the fields of selectedForm, and this is probably simpler because you don’t need a custom attribute — just take only the fields whose name starts with “questionX”.
this is a quick & easy way of doing it.. there are some suggestions to investigate LINQ, and I'd go with those first.
for (int i = 0; i < 150; i++)
{
if (!String.IsNullOrEmpty((string)reader["questionX" + i.ToString()].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX" + i.ToString()];}
}
... though this wouldn't be any good with the
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
lines
so I hear two questions:
- how to deal with null coming from IDataReader?
- how to deal with multiple fields?
Lets start with simple one. Define yourself a helper method:
public static T IsDbNull<T>(object value, T defaultValue)
{
return (T)(value is DBNull ? defaultValue : value);
}
then use it:
entity.patientUnderMdTreatment = IsDbNull<string>(reader["question"], null);
Now how to map entity fields to the form? Well that really is up to you. You can either hardcode it or use reflection. The difference of runtime mapping vs compile-time is likely to be completely irrelevant for your case.
It helps if your form fields have identical names to ones in the DB, so you don't have to do name mapping on top of that (as in Timwi's post), but in the end you'll likely find out that you have to do validation/normalization on many of them anyway at which point hardcoding is really what you need, since there isn't a way to dynamically generate logic according to the changing spec. It doesn't matter if you'll have to rename 150 db fields or attach 150 attributes - in the end it is always a O(n) solution where n is number of fields.
I am still a little unsure why do you need to read data back. If you need to preserve user's input on form reload (due to validation error?) wouldn't it be easier/better to reload them from the request? Also are entity and selectedForm the same object type? I assume its not a db entity (otherwise why use reader at all?).
Its possible that there are some shortcuts you may take, but I am having hard time following what are you reading and writing and when.
I recommend using the NullableDataReader. It eliminates the issue.

Categories

Resources