Entity Framework execute stored procedure with params - entity-framework

Ok, hope to not get too many flags, but it's to annoying.
I have a method in my controller which calls a method from another class:
offerForCreate.Rating = CalculateRating.CreateRating(addOffer);
and entire called class :
public class CalculateRating
{
private readonly DataContext mainContext;
public CalculateRating(DataContext mainContext)
{
this.mainContext = mainContext;
}
// calcul rating oferte noi
public decimal CreateRating(OfferForCreate offer)
{
decimal rating = mainContext.Database.FromSql<decimal>("RatingCalculator", offer.locationId, offer.typeId);
return rating;
}
}
I get an error when try to execute this procedure:
Error CS1061: 'DatabaseFacade' does not contain a definition for 'FromSql' and no extension method 'FromSql' accepting a first argument of type 'DatabaseFacade' could be found
and another if I don't create an instance of CalculateRating class in my controller :
Controllers\AnnouncesController.cs(127,37): error CS0120: An object reference is required for the non-static field, method, or property 'CalculateRating.CreateRating(OfferForCreate)
Everywhere I see must specify the entity, but what entity I can specify if my stored procedure use multiple tables?
Asp.Net Core Web API

You can execute stored proc like this:
using (var command = mainContext.Database.GetDbConnection().CreateCommand())
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandText = "dbo.RatingCalculator";
var locationIdParam = new System.Data.SqlClient.SqlParameter("#locationId", System.Data.SqlDbType.Int);
locationIdParam .Value = offer.locationId;
//DO same for typeId parameter
//Params to Parameters collection
command.Parameters.Add(locationIdParam);
command.Connection.Open();
return (double)command.ExecuteScalar();
}
Controllers\AnnouncesController.cs(127,37): error CS0120: An object reference is required for the non-static field, method, or property 'CalculateRating.CreateRating(OfferForCreate)
This error is occuring because if you declare CalculateRating as static you can not reference in non-static field mainContext.
You should create an instance of your CalculateRating class using Dependency Injection. Here are steps:
Create an interface ICalculateRating
public interface ICalculateRating {
decimal CreateRating(OfferForCreate offer);
}
Update CalculateRating class to implement ICalculateRating
Register the DBContext and ICalculateRating mappings in ConfigureServices method of Startup.cs file like this:
services.AddDbContext<DbContext>(opts=> { opts.UseSqlServer("sqlserver conntection string") }, ServiceLifetime.Scoped);
services.AddTransient<ICalculateRating, CalculateRating>();
In your controller constructor, input an argument of type ICalculateRating which will be injected by Microsoft Dependency Injection framework at runtime:
private readonly ICalculateRating _calculateRating;
public MyController(ICalculateRating calculateRating) {
_calculateRating = calculateRating;
}
You can then call the method like this:
offerForCreate.Rating = _calculateRating.CreateRating(addOffer);

Related

What is required for EF Core's IMethodCallTranslator to work with `EF.Functions` to provide a custom function?

I'm trying to implement a custom IMethodCallTranslator in EF Core 3.1 with the Sqlite provider.
I have created:
An extension method off of this DbFunctions which is called at query time
An implementation of IMethodCallTranslator which is Translate not called
A derived RelationalMethodCallTranslatorProvider which I'm passing an instance of my IMethodCallTranslator. This constructor is hit. I also tried overriding RelationalMethodCallTranslatorProvider.Translate and this was not hit.
An implementation of IDbContextOptionsExtension (and its info class) which registers the RelationalMethodCallTranslatorProvider as a singleton IMethodCallTranslatorProvider.
All of this is added via OnConfiguring by getting the IDbContextOptionsBuilderInfrastructure form of the options.
Am I missing something? I've tried following: https://github.com/tkhadimullin/ef-core-custom-functions/tree/feature/ef-3.1-version in my code, yet it's invoking the extension method, not my translator.
Apparently, you can't pass a reference to your full entity class to the function for further processing:
Doesn't work:
public static string DoWork(this DbFunctions _, object entity, string key)
//...
dbContext.Items.Select(i => new { Entity = i, String = EF.Functions.DoWork(i, i.StringValue) });
Where this will:
public static string DoWork(this DbFunctions _, string key)
//...
dbContext.Items.Select(i => new { Entity = i, String = EF.Functions.DoWork(i.StringValue) });
Also, generics are supported so it would be a nice way to introspect call and get the type of the entity for the model in a RelationalMethodCallTranslatorProvider.
public static string DoWork<T>(this DbFunctions _, string key)
//...
dbContext.Items.Select(i => new { Entity = i, String = EF.Functions.DoWork<Item>(i.StringValue) });

Entity Framework Intercept Generate Migration Script

I use Entity Framework 6.2 Code First (.net framework 4.6.1) and I map few entities to view via Table Attribute. It works for select operations and I handle Insert/Update/Delete with writing trigger to view at sql server side. It works as expected, however when I add a new migration, Entity Framework generate RenameTable scripts for used Table Attribute (actuallyis expected behavior for EF). But I want to intercept migration generation and change these entities tableName to original name.
my code like;
[MapToView("Users","UsersView")]
public class User
{
...
}
I wrote MapToView Attribute, this attribute inherited by TableAttribute and pass to second parameter to TableAttribute. I create this Attribute because if I intercept migration generation, return original table name with this attribute parameters.
In this case when I run "add-migration migrationName" it creates migration scripts like this;
RenameTable(name: "dbo.Users", newName: "UsersView");
but i want to create empty migration when I run "add-migration migrationName" script.
anyone can help me?
I solve the problem.
First: Problem is; When I Map Entity to View EF Code-first generate migration with ViewName. This is problem because I want to use View Instead of Table. So I solve problem with this instructions;
1- I Create BaseEntityConfiguration that Inherited from EntityTypeConfiguration and all entity configuration classes are inherited by.
for example:
public class UserConfig: BaseEntityConfiguration<User> //Generic Type is Entity
{
public UserConfig()
{
}
}
2- I Create MapToViewAttribute that inherited by TableAttribute
public class MapToViewAttribute : TableAttribute
{
public string TableName { get; }
public string ViewName { get; }
public MapToViewAttribute(string tableName, string viewName) : base(viewName)
{
TableName = tableName;
ViewName = viewName;
}
}
3- I Use MapToViewAttribute for example User Entity to use View.
[MapToView("User","UserView")]
public class User
{
...
}
And in BaseEntityConfiguration's Constructor I Get Generic Type and custom attributes. If any entity has MapToView Attribute, I pass to TableName parameter to ToTable Method. So at runtime EF uses View for these entities but doesn't create migration with RenameTable for these entities.
protected BaseEntityConfiguration()
{
var baseType = typeof(TEntityType);
var attributes = baseType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr.GetType() == typeof(MapToViewAttribute))
{
var tableName = ((MapToViewAttribute)attr).TableName;
ToTable(tableName);
}
}
}
Last EF don't use your configuration files, so you must tell the EF to use this in DbContext class's InternalModelCreate method.
My implementation like this;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly()
.GetTypes().Where(IsConfigurationType);
foreach (var type in typesToRegister)
{
dynamic configurationInstance = type.BaseType != null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(BaseEntityConfiguration<>)
? Activator.CreateInstance(type, culture)
: Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name));
BaseDbContext.InternalModelCreate(modelBuilder);
}
But if you use this approach you must create Insert, Update and Delete Triggers/Rule (if you use SQLServer trigger is an option but if you use postgresql rule is better option) because EF uses this view for insert, update and delete operations.

Specify connection string for a query with DbContextScope project

I am currently using Mehdi El Gueddari's DbContextScope project, I think by the book, and it's awesome. But I came across a problem I'm unsure how to solve today. I have a query that I need to execute using a different database login/user because it requires additional permissions. I can create another connection string in my web.config, but I'm not sure how to specify that for this query, I want to use this new connection string. Here is my usage:
In my logic layer:
private static IDbContextScopeFactory _dbContextFactory = new DbContextScopeFactory();
public static Guid GetFacilityID(string altID)
{
...
using (_dbContextFactory.CreateReadOnly())
{
entity = entities.GetFacilityID(altID)
}
}
That calls into my data layer which would look something like this:
private AmbientDbContextLocator _dbcLocator = new AmbientDbContextLocator();
protected CRMEntities DBContext
{
get
{
var dbContext = _dbcLocator.Get<CRMEntities>();
if (dbContext == null)
throw new InvalidOperationException("No ambient DbContext....");
return dbContext;
}
}
public virtual Guid GetFaciltyID(string altID)
{
return DBContext.Set<Facility>().Where(f => f.altID = altID).Select(f => f.ID).FirstOrDefault();
}
Currently my connection string is set in the default way:
public partial class CRMEntities : DbContext
{
public CRMEntities()
: base("name=CRMEntities")
{}
}
Is it possible for this specific query to use a different connection string and how?
I ended up modifying the source code in a way that feels slightly hacky, but is getting the job done for now. I created a new IAmbientDbContextLocator with a Get<TDbContext> method override that accepts a connection string:
public TDbContext Get<TDbContext>(string nameOrConnectionString) where TDbContext : DbContext
{
var ambientDbContextScope = DbContextScope.GetAmbientScope();
return ambientDbContextScope == null ? null : ambientDbContextScope.DbContexts.Get<TDbContext>(nameOrConnectionString);
}
Then I updated the DbContextCollection to pass this parameter to the DbContext's existing constructor overload. Last, I updated the DbContextCollection maintain a Dictionary<KeyValuePair<Type, string>, DbContext> instead of a Dictionary<Type, DbContext> as its cached _initializedDbContexts where the added string is the nameOrConnectionString param. So in other words, I updated it to cache unique DbContext type/connection string pairs.
Then I can get at the DbContext with the connection I need like this:
var dbContext = new CustomAmbientDbContextLocator().Get<CRMEntities>("name=CRMEntitiesAdmin");
Of course you'd have to be careful your code doesn't end up going through two different contexts/connection strings when it should be going through the same one. In my case I have them separated into two different data access class implementations.

Cast class to base interface via reflection cause exception

I'm loading a .NET assembly dinamically via reflection and I'm getting all the classes that it contains (at the moment one). After this, I'm trying to cast the class to an interface that I'm 100% sure the class implements but I receive this exception: Unable to cast object of type System.RuntimeType to the type MyInterface
MyDLL.dll
public interface MyInterface
{
void MyMethod();
}
MyOtherDLL.dll
public class MyClass : MyInterface
{
public void MyMethod()
{
...
}
}
public class MyLoader
{
Assembly myAssembly = Assembly.LoadFile("MyDLL.dll");
IEnumerable<Type> types = extension.GetTypes().Where(x => x.IsClass);
foreach (Type type in types)
{
((MyInterface)type).MyMethod();
}
}
I have stripped out all the code that is not necessary. This is basically what I do. I saw in this question that Andi answered with a problem that seems the same mine but I cannot anyway fix it.
You are trying to cast a .NET framework object of type Type to an interface that you created. The Type object does not implement your interface, so it can't be cast. You should first create a specific instance of your object, such as through using an Activator like this:
// this goes inside your for loop
MyInterface myInterface = (MyInterface)Activator.CreateInstance(type, false);
myInterface.MyMethod();
The CreateInstance method has other overloades that may fit your needs.

Adding custom property to object returned from WCF RIA Services

I have a stored procedure in my Entity Framework Model. I've added a Function Import and mapped the results to a Complex Type.
I want to add an extra property to this Complex type, that I'll populate in my Domain Service, not coming back from the stored procedure. I added a myClass.shared.cs file and implemented added the property like so:
//myClass.shared.cs
public partial class myClass
{
public string myProperty {get;set;}
}
I populate this in my domain service when I return the object, e.g.:
public myClass GetMyClass(int myClassID)
{
myClass theClass= this.ObjectContext.StoredProc(myClassID).FirstOrDefault();
class.myProperty = 12345;
return theClass;
}
When I get the return values of this method on the client side theClass.myProperty is always null but all values from the stored procedure are populated, am I missing something?
I've tried decorating the myProperty with the [DataMember] attribute but this throws the error:
"The type 'myClass' already contains a
definition for 'myProperty'"
How can I get this to return the value set in the Domain Service to the client?
There was no need to put this in the shared.cs class. The shared.cs class copies the actual code over to the client side and is useful for adding methods etc. but to add a new property, all I had to do was add a partial class (NOT in myClass.shared.cs) and decorate it with DataMember.
public partial class myClass
{
[DataMember]
public string myProperty {get;set;}
}