Is it possible to get to the current connection from inside your code first "table" class?
I'm trying to write a MVC Multi Tenancy app (1 app, many db's) the easiest way i can think of is to pass the connection string (or tenant name) when creating the dbcontext (i've looked at other ways to do this, but don't really understand them). However once i go into the table class I cannot then access the current db connection to perform other actions i need.
Example code
public class ConnectionContext : DbContext
{
public ConnectionContext(String connectionString)
: base(connectionString)
{
if (!this.Database.Exists())
throw new Exception("Database does not exist");
}
public DbSet<Table1> Table1 { get; set; }
}
[Table("Table1")]
public class Table1
{
[Key]
public String Column1 { get; set; }
public String Column2 { get; set; }
public Int32 GetNoOfColumns()
{
using (var conn = new ConnectionContext("???")) // <-- **issue here**
{
//Code to get info
return 0;
}
}
public void Authorize()
{
using (var conn = new ConnectionContext("???")) // <-- **issue here**
{
this.Column2 = "Authorized";
conn.Entry(this).State = EntityState.Modified;
conn.SaveChanges();
}
}
}
Not sure it's possible in the way that I was looking at.
You don't have to make a constructor with a connection string parameter, you can create your dbcontext class like this:
public class ConnectionContext : DbContext
{
public ConnectionContext()
: base("nameOrConnectionString")
{
if (!this.Database.Exists())
throw new Exception("Database does not exist");
}
public DbSet<Table1> Table1 { get; set; }
}
And then call your connection context this way:
using (var conn = new ConnectionContext())
{
}
Related
Im trying to create my database after finnishing up my classes, getting error message
"Unable to create an object of type 'HamsterDbContext'. For the different patterns supported at design time"
public class HamsterDbContext : DbContext
{
public HamsterDbContext(DbContextOptions<HamsterDbContext> options) : base(options) { }
public virtual DbSet<Hamster> Hamsters { get; set; }
public virtual DbSet<Cage> Cages { get; set; }
public virtual DbSet<ExerciseArea> ExerciseArea { get; set; }
}
internal class DatabaseHelper
{
public static HamsterDbContext NewContext()
{
const string host = "(LocalDB)\\MSSQLLocalDB";
const string database = "HamsterDaycare";
var connectionString = $"Data Source={host};Initial Catalog={database};Integrated Security=True;";
var dbContextBuilder = new DbContextOptionsBuilder<HamsterDbContext>();
dbContextBuilder.UseSqlServer(connectionString);
return new HamsterDbContext(dbContextBuilder.Options);
}
}
Looks like this, typing add-migration CreateDatabase, what am i doing wrong here?
Fix your string connection. Instead DataSource, put server. Your ConnectionString must be this way:
{
"ConnectionStrings": {
"DefaultConnection": "server=yourserver;database=yourdatabase;user=youruser;password=yourpassword"},
...
},
I'm new at net core environment, and I must get an data from my database(postgesql),
I have a data in db but i must only get him in json format,
so at the begining i create a model class with field this same as in db:
namespace crud.Models
{
public class User: DbContext
{
public User(DbContextOptions<User> options): base(options) { }
public int Id { get; set; }
public string Firstname{ get; set; }
public string Lastname { get; set; }
public string City { get; set; }
public string Phone{ get; set; }
}
}
after this i create an repository(in this way i create db controllers in java/spring):
namespace crud.Service
{
public interface IUserRepository : IDisposable
{
User GetUserById(int id);
User GetUserByCity(string city);
}
}
and now I don't know what I must going next, I try to implement this interface in service like this:
namespace crud.Service
{
public class UserRepository : IUserRepository
{
public void Dispose()
{
throw new System.NotImplementedException();
}
public User GetUserByCity(string city)
{
throw new System.NotImplementedException();
}
public User GetUserById(int id)
{
throw new System.NotImplementedException();
}
}
}
but i don't now what i must going next,
can anyone tell me what my service and controller should look like?
ps: my connection to db:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkNpgsql().AddDbContext<User>(opt =>
opt.UseNpgsql(Configuration.GetConnectionString("User")));
}
"ConnectionStrings":{
"User" : "User ID=root; Password=postgres; Host=localhost; Port=5432; Database=postgres; Integrated Security=true, Pooling=true"
},
You need to look at the service and repository patterns for ASP.NET MVC-Core, because they are two very important design patterns in .NET applications that interact with data. The repository will actually interact with your database via the Entity Framework Core (similar to Hibernate). Your service layer is an abstraction over your repository. If you're using the Entity Framework Core then your DbContext will represent your database connection. Through your DbContext you'll interact with entities, which are POCOs (Plain Ole C# Objects)
https://exceptionnotfound.net/the-repository-service-pattern-with-dependency-injection-and-asp-net-core/
But here I'm taking a few liberties. I'm assuming your DbContext is called MyDbContext and it contains a Users entity. I've simplified a few things.
public class UserRepository : IUserRepository
{
MyDbContext db = new MyDbContext();
public void Dispose()
{
if(db != null)
{ db.Dispose();}
}
public User GetUserByCity(string city)
{
return db.Users.Where(c => c.City.Equals(city).First();
}
}
Firstly, you must create a entity, which is User in your case.
public class User
{
public int Id { get; set; }
public string Firstname{ get; set; }
public string Lastname { get; set; }
public string City { get; set; }
public string Phone{ get; set; }
}
Secondly, create the DbContext
public class UserContext : DbContext
{
public UserContext(DbContextOptions<UserContext> options): base(options)
{
}
public DbSet<User> Users { get; set; }
}
Next, add the UserContext in the Startup.cs
services.AddEntityFrameworkNpgsql().AddDbContext<UserContext>(opt =>
opt.UseNpgsql(Configuration.GetConnectionString("User")));
Lastly, You must inject the UserContext into the UserRepository (using dependency injection) and you can get your data from your database using this UserRepository.
public class UserRepository : IUserRepository
{
private readonly UserContext _context;
public UserRepository(UserContext context)
{
_context = context;
}
public void Dispose()
{
throw new System.NotImplementedException();
}
public List<User> GetUserByCity(string city)
{
return _context.Users.Where(u => u.City == city).ToList();
}
public User GetUserById(int id)
{
return _context.Users.FirstOrDefault(u => u.id == id);
}
}
You should take some time to look into Entityframework, beign the major ORM (object relational Mapper ) for c# application's https://learn.microsoft.com/en-us/ef/
static void HasRows(SqlConnection connection)
{
using (connection)
{
SqlCommand command = new SqlCommand(
"SELECT CategoryID, CategoryName FROM Categories;",
connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
Console.WriteLine("{0}\t{1}", reader.GetInt32(0),
reader.GetString(1));
}
}
else
{
Console.WriteLine("No rows found.");
}
reader.Close();
}
}
When trying to use Contrib's CRUD methods in an object where the properties are in an inherited object I get an
Entity must have at least one [Key] or [ExplicitKey] property
error. Here is a simplified version of my objects:
public class BaseObject
{
public string Delete()
{
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["con"].ConnectionString))
{
db.Delete(this);
}
}
}
and this
public class Product: BaseObject
{
[Key]
public int id { get; set; }
public string title { get; set; }
public string name { get; set; }
}
I get the error when I execute:
Product product = new Product() {id = 1};
product.Delete();
If I Remove the inheritance and move the Delete() method into the Product object it works flawlessly.
Any ideas?
Your BaseObject is not linked to any table so calling Delete() on it could not be understood by Dapper.
I think that in your case, I would simply use an extension method:
public static class BaseObjectExtensions
{
public static string Delete<T>(this T theObject) where T : BaseObject
{
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["con"].ConnectionString))
{
db.Delete(theObject);
}
}
}
Scenario: Intranet app. Windows authentication. EF 6.1.3. Databases: SQL Server Compact Edition and MS Access. VS Studio 2013.
The solution has 3 projects:
EnqueteWeb.UI - ASP.NET web application;
EnqueteWeb.Dominio - class library for the application domain;
ControleDeAcessoGeral - class library to get data of the user logged from Active Directory, and include/update/delete/list some users that perform some special actions on the app.
As the access control to the app is based on a SQL Server Compact Edition database, I have EntityFramework installed in ControleDeAcessoGeral. I want to have all the methods regarding to users in a class in this project. And so I did it.
This ControleDeAcessoGeral project is defined like this:
Aplicacao
- Corp.cs (methods to deal with Active Directory stuff)
- UsuariosApp.cs (methods to deal with the SQL Server CE database)
Contexto
- DBControleDeAcesso.cs (defines the context)
- InicializaControleDeAcesso.cs (fill in initial data to the
DBControleDeAcesso database)
Entidades
- Perfil.cs (profiles that a user can have on the app)
- Usuarios.cs (users that may perform some actions on the app)
- UsuarioAD.cs (Active Directory user and its data)
The DBControleDeAcesso.cs class has the following code:
using ControleDeAcessoGeral.Models.Entidades;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ControleDeAcessoGeral.Models.Contexto
{
public class DBControleDeAcesso : DbContext
{
public DBControleDeAcesso() : base("ControleDeAcessoContext") { }
public DbSet<Perfil> Perfis { get; set; }
public DbSet<Usuario> Usuarios { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
The entities classes are the following:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ControleDeAcessoGeral.Models.Entidades
{
public class Usuario
{
[Key]
public string Logon { get; set; }
public string Nome { get; set; }
[Display(Name="Órgão")]
public string Orgao { get; set; }
public string Email { get; set; }
[StringLength(maximumLength: 4)]
public string Depto { get; set; }
[Display(Name = "Perfis")]
public virtual List<Perfil> Perfis { get; set; }
public Usuario()
{
this.Perfis = new List<Perfil>();
}
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ControleDeAcessoGeral.Models.Entidades
{
public class Perfil
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Por favor, informe o NOME DO perfil.")]
[StringLength(maximumLength: 25)]
public string Nome { get; set; }
[StringLength(maximumLength: 255)]
[Display(Name = "Descrição")]
public string Descricao { get; set; }
public virtual List<Usuario> Usuarios { get; set; }
public Perfil()
{
this.Usuarios = new List<Usuario>();
}
}
}
And the UsuariosApp.cs class is as bellow (for the sake of brevity, I'll show only the methods that concerns to the issue):
using ControleDeAcessoGeral.Models.Contexto;
using ControleDeAcessoGeral.Models.Entidades;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace ControleDeAcessoGeral.Models.Aplicacao
{
public class UsuariosApp
{
private DBControleDeAcesso db { get; set; }
public UsuariosApp()
{
db = new DBControleDeAcesso();
}
public void SalvarUsuario(Usuario usuario)
{
db.Usuarios.Add(usuario);
db.SaveChanges();
}
public Perfil LocalizarPerfil(int id)
{
return db.Perfis.Find(id);
}
}
}
The action that tries to save a user (Usuarios.cs) in the SQL Server CE database is in AdministracaoController and has the following code:
using ControleDeAcessoGeral.Models.Aplicacao;
using ControleDeAcessoGeral.Models.Entidades;
using EnqueteWeb.UI.Models;
using EnqueteWeb.UI.ViewModels;
using System.Linq;
using System.Web.Mvc;
namespace EnqueteWeb.UI.Controllers
{
public class AdministracaoController : Controller
{
[HttpPost]
public ActionResult CriarUsuarioNaApp(UsuarioViewModel model)
{
foreach (var item in model.PerfisSelecionados)
{
Perfil perfil = new UsuariosApp().LocalizarPerfil(item);
model.Usuario.Perfis.Add(perfil);
}
if (ModelState.IsValid)
{
new UsuariosApp().SalvarUsuario(model.Usuario);
return RedirectToAction("Usuarios");
}
return View(model);
}
}
}
So, when this action CriarUsuarioNaApp is invoked and the method SalvarUsuario(model.Usuario) runs, the following error occurs:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
I've read a few about this on web but, unfortunately, I still couldn't make it works.
Hope a wise and good soul will show me the way.
Thanks for your attention.
Paulo Ricardo Ferreira
The problem arises from the fact that you do not dispose of the first DbContext instance (from which you load the profile entities) prior to attaching said entities to the second DbContext instance.
To fix (and some additional suggestions):
have UsuariosApp implement IDisposable and dispose your instance of the DbContext db when disposing UsuariosApp
wrap your newly-disposable UsuariosApp instance in a using statement and use this single instance for both your Perfil loading and Usuario saving logic
optimize Perfil loading by loading all values with single call
validate ModelState.IsValid immediately
Something like this:
public class UsuariosApp : IDisposable
{
private DBControleDeAcesso db { get; set; }
public UsuariosApp()
{
db = new DBControleDeAcesso();
}
public void SalvarUsuario(Usuario usuario)
{
db.Usuarios.Add(usuario);
db.SaveChanges();
}
public Perfil LocalizarPerfil(int id)
{
return db.Perfis.Find(id);
}
public IEnumerable<Perfil> LocalizarPerfiles( IEnumerable<int> ids )
{
return db.Perfils.Where( p => ids.Contains( p.Id ) )
.ToArray();
}
private bool _disposed = false;
protected virtual void Dispose( bool disposing )
{
if( _disposed )
{
return;
}
if( disposing )
{
db.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
}
public ActionResult CriarUsuarioNaApp( UsuarioViewModel model )
{
// validate model state first
if( ModelState.IsValid )
{
// use single, disposable repo/uow instance
using( var uapp = new UsuariosApp() )
{
// get all profiles in a single call, no loop required
var perfils = uapp.LocalizarPerfiles( model.PerfisSelecionados );
model.Usuario.Perfis.AddRange( perfils );
uapp.SalvarUsuario( model.Usuario );
}
return RedirectToAction( "Usuarios" );
}
return View( model );
}
Let me know if that doesn't solve your problem.
I stole the following code from https://stackoverflow.com/a/14988549/294022 .
This seems a great abstraction. However there is a single problem. What if you want to combine multiple service calls?
This works fine combining DAL calls. But for service? Is there a solution?
public class Foo //POCO for data access
{
//Add Attributes for Ormlite
public int Id { get; set; }
}
public class Bar //POCO for data access
{
//Add Attributes for Ormlite
public int Id { get; set; }
}
//your request class which is passed to your service
public class DeleteById
{
public int Id { get; set; }
}
public class FooBarService : MyServiceBase //MyServiceBase has resusable method for handling transactions.
{
public object Post(DeleteById request)
{
DbExec(dbConn =>
{
dbConn.DeleteById<Foo>(request.Id);
dbConn.DeleteById<Bar>(request.Id);
});
return null;
}
}
public class MyServiceBase : Service
{
public IDbConnectionFactory DbFactory { get; set; }
protected void DbExec(Action<IDbConnection> actions)
{
using (var dbConn = DbFactory.OpenDbConnection())
{
using (var trans = dbConn.OpenTransaction())
{
try
{
actions(dbConn);
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw ex;
}
}
}
}
}
I think you need to go toward implementing a UnitOfWork that can be passed/shared amongst those who wish to partake in a single 'transaction'.