Get value from sql select if parameter may be null - entity-framework

Looking for a method to get value of first row in first column from arbitary sql query in ASP.NET MVC Core application using EF Core.
Like
var total = ctx.ExecuteScalar<decimal?>(#"select ... where p1={0}", null);
Tried https://github.com/weitzhandler code from https://github.com/dotnet/EntityFramework.Docs/issues/969 with Npgsql EF Provider (changed to synchronous):
public partial class EevaContext : DbContext
{
public T ExecuteScalar<T>(string rawSql, params object[] parameters)
{
var context = this;
var conn = context.Database.GetDbConnection();
using var command = conn.CreateCommand();
command.CommandText = rawSql;
if (parameters != null)
foreach (var p in parameters)
command.Parameters.Add(p ?? DbNull.Value);
conn.Open();
return (T)command.ExecuteScalar();
}
If parameter value is null line
command.Parameters.Add(p ?? DbNull.Value);
throws error
InvalidCastException: The value "" is not of type "NpgsqlParameter"
and cannot be used in this parameter collection.
Npgsql.NpgsqlParameterCollection.Cast(object value)
Which is best way to run such query ?
Maybe EF core has some builtin method? Or is it better to use some type and inject it into EF Context is OnModelCreated method ?
If there is no better way how to fix this code so that parameters with null values are also accepted ?
There are lot of such queries with lot of parameters in application and re-qriting all of them in huge work. WebMatrix QueryValue was used in .NET 4 which allows null values in parameters.

command.Parameters.Add(p ?? DbNull.Value);
Is p an NpgsqlParameter, or some value that you want to send?? You cannot simply add values (or null) to command.Parameters.Add - you need to either wrap these with NpgsqlParameter or use command.Parameters.AddWithValue() instead. Note that the NpgsqlParameter also needs to have its ParameterName set correctly, correponding to the placeholders in your raw SQL (e.g. #param1).

Related

In EF 6 DBInterception, how to get the actual value from DbModificationClause clause?

I'm researching with encrypting an EF 6 db column using db interceptors and custom annotation on the ef entity: https://www.codeguru.com/csharp/soft-deleting-entities-cleanly-using-entity-framework-6-interceptors/
However, the guide's example is using a static string to replace the value prior to inserting to the database. I need the original value to encrypt it and then use the new encrypted value to be inserted to the db but I can't seem to figure out how to get the original value.
public static DbModificationClause UpdateIfMatch(this DbModificationClause clause, string property)
{
var propertyExpression = (DbPropertyExpression)((DbSetClause)clause).Property;
// Get the original value to be inserted to the DB
// Encrypt that value and use it instead
var newEncryptedValue = propertyExpression.GetValue()?
if (propertyExpression.Property.Name == property)
{
return DbExpressionBuilder.SetClause(propertyExpression, DbExpression.FromString(newEncryptedValue));
}
return clause;
}

EF deferred execution using SQLquery as input

I am trying to get an entity with EF by having an initial sql as input.
I tried the context.Entities.SQLQuery method but this returns a DBSet when I require an IQueriable.
I learned that I cannot transform DBSet to IQueryable because the first is already a result of data while the second is the container for the results of a "query" (executed yet or not). Correct me if i'm wrong :)
So I thought that when I write the following lambda I get the resulting query:
db.MyTable.Where(x => x.id == "123")
Becomes:
SELECT * FROM myTable WHERE id = '123'
With this I thought if I can set directly my query without needing to set my lambda...
Is that an option?
Or an alternative?
Thanks!
It's a bit unclear what you mean:
I am trying to get an entity with EF having an initial sql as input.
I'd interpret this, as that you have an SQL statement as input of something and you want to get an entity framework entity that would have this statement (whatever having a statement means)? Not understandable!
I learned that I cannot transform DBSet to IQueryable because the
first (the DbSet) is already a result of data while the second (the IQueryable) is the container for the results of a "query"
NOT!
Every DbSet<T> implements IQueryable<T>, meaning that if you have an object of class DbSet<t>, this object implements all functionality of IQueryable<T>. Just using this IQueryable does not execute the query. The query will only be executed once the first element of the sequence if requested.
using (var dbContext = new MyDbcontext())
{
var result = dbContext.MyItems
.Where(item => ...)
.Select(item => new
{
X = item.Property1,
Y = item.Property2,
...
};
Until here, the first element of the sequence is not asked, the query is not performed yet. No communication with the database was needed (except to create the dbContext object)
Only if you use execution functions like ToList(),Count(), First(), Max(), etc, the query is performed.
You can check this, because you get exceptoin if you do these kind of functions after the using block:
Wrong
IQueryable largeItems;
using (var dbContext = new MyDbcontext())
{
largeItems = dbContext.MyItems
.Where(item => item.Size > 1000);
// query not executed yet
}
int nrOfLargeItems = largeItems.Count();
// exception, query executed after dbContext is disposed
correct
int nrOfLargeItems;
using (var dbContext = new MyDbcontext())
{
var largeItems = dbContext.MyItems
.Where(item => item.Size > 1000);
// query not executed yet
nrOfLargeItems = largeItems.Count();
// the query is performed
}
Conclusion: users of a DbSet<T> inside a dbContext can use the DbSet<T> as if it was an IQueryable<T>, the query will not be executed until you perform any function that needs the first element of the query.
This includes complex functions like Join, GroupBy, OrderBy, etc. You can recognize these functions because MSDN add the following to the remarks section
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach.

EF Core 2.0: How to discover the exact object, in object graph, causing error in a insert operation?

I have a complex and big object graph that I want to insert in database by using a DbContext and SaveChanges method.
This object is a result of parsing a text file with 40k lines (around 3MB of data). Some collections inside this object have thousands of items.
I am able to parse the file correctly and add it to the context so that it can start tracking the object. But when I try to SaveChanges, it says:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: String or binary data would be truncated.
I would like to know if there is a smart and efficient way of discovering which object is causing the issue. It seems that a varchar field is too little to store the data. But it's a lot of tables and fields to check manually.
I would like to get a more specific error somehow. I already configured an ILoggerProvider and added the EnableSensitiveDataLogging option in my dbContext to be able to see which sql queries are being generated. I even added MiniProfiler to be able to see the parameter values, because they are not present in the log generated by the dbContext.
Reading somewhere in the web, I found out that in EF6 there is some validation that happens before the sql is passed to the database to be executed. But it seems that in EF Core this is not available anymore. So how can I solve this?
After some research, the only approach I've found to solve this, is implementing some validation by overriding dbContext's SaveChanges method. I've made a merge of these two approaches to build mine:
Implementing Missing Features in Entity Framework Core - Part 3
Validation in EF Core
The result is...
ApplicationDbContext.cs
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ValidateEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
ValidateEntities();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void ValidateEntities()
{
var serviceProvider = this.GetService<IServiceProvider>();
var items = new Dictionary<object, object>();
var entities = from entry in ChangeTracker.Entries()
where entry.State == EntityState.Added || entry.State == EntityState.Modified
select entry.Entity;
foreach (var entity in entities)
{
var context = new ValidationContext(entity, serviceProvider, items);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(entity, context, results, true)) continue;
foreach (var result in results)
{
if (result == ValidationResult.Success) continue;
var errorMessage = $"{entity.GetType().Name}: {result.ErrorMessage}";
throw new ValidationException(errorMessage);
}
}
}
Note that it's not necessary to override the other SaveChanges overloads, because they call these two.
The Error tells you that youre writing more characters to a field than it can hold.
This error for example would be thrown when you create a given field as NVARCHAR(4) or CHAR(4) and write 'hello' to it.
So you could simply check the length of the values you read in to find the one which is causing your problem. There is at least on which is too long for a field.

Execute StoredProcedure in CodeFirst 4.1

I understand stored procedures mapping is not supported by my understanding is that I should be able to call stored procedures.
I have quite a few complex stored procedures and with the designer I could create a complex type and I was all good.
Now in code first let's suppose I have the following stored procedure, just put together something silly to give an idea. I want to return a student with 1 address.
In code I have A Student and Address Entity. But no StudentAddressEntity as it's a link table.
I have tried the following but I get an error
Incorrect syntax near '."}
System.Data.Common.DbException {System.Data.SqlClient.SqlException}
ALTER Procedure [dbo].[GetStudentById]
#StudentID int
AS
SELECT *
FROM Student S
left join StudentAddress SA on S.Studentid = sa.studentid
left join Address A on SA.AddressID = A.AddressID
where S.StudentID = #StudentID
C# code:
using (var ctx = new SchoolContext())
{
var student = ctx.Database.SqlQuery<Student>("GetStudentById,#StudentID",
new SqlParameter("StudentID", id));
}
Any examples out there how to call sp and fill a complexType in code first, using out parameters etc.. Can I hook into ADO.NET?
Trying just an SP that returns all students with no parameters I get this error
System.SystemException = Cannot create a value for property
'StudentAddress' of type
'CodeFirstPrototype.Dal.Address'. Only
properties with primitive types are
supported.
Is it because I have in a way ignore the link table?
Any suggestions?
I believe that your exception actually is:
Incorrect syntax near ','.
because this is invalid statement: "GetStudentById,#StudentID". It should be without comma: "GetStudentById #StudentID".
The problem with stored procedures in EF is that they don't support loading navigation properties. EF will materialize only the main entity and navigation properties will not be loaded. This is solved for example by EFExtensions. EFExtensions are for ObjectContext API so you will have to check if it is also usable for DbContext API.
Using EFExtentions it will look something like
using (var context = new SchoolContext())
{
var command = context.CreateStoreCommand("GetStudentById", CommandType.StoredProcedure,
new SqlParameter("StudentID", id));
using (command.Connection.CreateConnectionScope())
using (var reader = command.ExecuteReader())
{
// use the reader to read the data
// my recommendation is to create a Materializer using EFExtensions see
// http://blogs.msdn.com/b/meek/archive/2008/03/26/ado-entity-framework-stored-procedure-customization.aspx
// ex
var student = Student.Materializer.Materialize(reader).SingleOrDefault();
return student;
}
}

Add index with entity framework code first (CTP5)

Is there a way to get EF CTP5 to create an index when it creates a schema?
Update: See here for how EF 6.1 handles this (as pointed out by juFo below).
You can take advantage of the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
As some mentioned in the comments to Mortezas answer there is a CreateIndex/DropIndex method if you use migrations.
But if you are in "debug"/development mode and is changing the schema all the time and are recreating the database every time you can use the example mentioned in Morteza answer.
To make it a little easier, I have written a very simple extension method to make it strongly typed, as inspiration that I want to share with anyone who reads this question and maybe would like this approach aswell. Just change it to fit your needs and way of naming indexes.
You use it like this: context.Database.CreateUniqueIndex<User>(x => x.Name);
.
public static void CreateUniqueIndex<TModel>(this Database database, Expression<Func<TModel, object>> expression)
{
if (database == null)
throw new ArgumentNullException("database");
// Assumes singular table name matching the name of the Model type
var tableName = typeof(TModel).Name;
var columnName = GetLambdaExpressionName(expression.Body);
var indexName = string.Format("IX_{0}_{1}", tableName, columnName);
var createIndexSql = string.Format("CREATE UNIQUE INDEX {0} ON {1} ({2})", indexName, tableName, columnName);
database.ExecuteSqlCommand(createIndexSql);
}
public static string GetLambdaExpressionName(Expression expression)
{
MemberExpression memberExp = expression as MemberExpression;
if (memberExp == null)
{
// Check if it is an UnaryExpression and unwrap it
var unaryExp = expression as UnaryExpression;
if (unaryExp != null)
memberExp = unaryExp.Operand as MemberExpression;
}
if (memberExp == null)
throw new ArgumentException("Cannot get name from expression", "expression");
return memberExp.Member.Name;
}
Update: From version 6.1 and onwards there is an [Index] attribute available.
For more info, see http://msdn.microsoft.com/en-US/data/jj591583#Index
This feature should be available in the near-future via data annotations and the Fluent API. Microsoft have added it into their public backlog:
http://entityframework.codeplex.com/workitem/list/basic?keywords=DevDiv [Id=87553]
Until then, you'll need to use a seed method on a custom Initializer class to execute the SQL to create the unique index, and if you're using code-first migrations, create a new migration for adding the unique index, and use the CreateIndex and DropIndex methods in your Up and Down methods for the migration to create and drop the index.
Check my answer here Entity Framework Code First Fluent Api: Adding Indexes to columns this allows you to define multi column indexes by using attributes on properties.