I try to open a sqlite db from a .netstandard library project that is used by a xamarin forms project (uwp and android) I have only tried the uwp project and get a unable to open database exception
I have tried to use the path of the Personal folder und i have tried to open the connection via Sqliteconnection.
Full Project ist available here: https://github.com/blndr83/OutlookCalender
internal class DatabaseProvider
{
private static ISessionFactory _sessionFactory;
private static Configuration _configuration;
private static HbmMapping _mapping;
public static ISession OpenSession()
{
//Open and return the nhibernate session
return SessionFactory.OpenSession();
}
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
//Create the session factory
_sessionFactory = Configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
public static Configuration Configuration
{
get
{
if (_configuration == null)
{
//Create the nhibernate configuration
_configuration = CreateConfiguration();
}
return _configuration;
}
}
public static HbmMapping Mapping
{
get
{
if (_mapping == null)
{
//Create the mapping
_mapping = CreateMapping();
}
return _mapping;
}
}
private static Configuration CreateConfiguration()
{
var configuration = new Configuration();
//Loads properties from hibernate.cfg.xml
configuration.Configure();
IDictionary<string, string> props = new Dictionary<string, string>
{
{ "connection.connection_string", #"Data Source=Calendar.db;FailIfMissing=false;New=false;Compress=true;Version=3"},
{ "connection.driver_class", "NHibernate.Driver.SQLite20Driver" },
{ "dialect", "NHibernate.Dialect.SQLiteDialect" },
{ "connection.provider", "NHibernate.Connection.DriverConnectionProvider" },
{ "show_sql", "false" }
};
configuration.SetProperties(props);
configuration.AddDeserializedMapping(Mapping, null);
return configuration;
}
private static HbmMapping CreateMapping()
{
var mapper = new ModelMapper();
//Add the person mapping to the model mapper
mapper.AddMappings(new List<System.Type> { typeof(EventModelMap) });
//Create and return a HbmMapping of the model mapping in code
return mapper.CompileMappingForAllExplicitlyAddedEntities();
}
}
public class Repository : IRepository
{
private readonly ISession _session;
public Repository()
{
_session = DatabaseProvider.OpenSession();
var schemaUpdate = new SchemaUpdate(DatabaseProvider.Configuration);
schemaUpdate.Execute(Console.WriteLine, true);
}
public void Delete<T>(T entity) where T : Entity
{
using (var transaction = _session.BeginTransaction())
{
_session.Delete(entity);
transaction.Commit();
}
}
public T Find<T>(Expression<Func<T,bool>> expression) where T : Entity
{
return _session.QueryOver<T>().Where(expression).SingleOrDefault();
}
public async Task<IList<T>> FindAll<T>(Expression<Func<T, bool>> expression) where T : Entity
{
return await _session.QueryOver<T>().Where(expression).ListAsync();
}
public void Save<T>(T entity) where T : Entity
{
using (var transaction = _session.BeginTransaction())
{
_session.Save(entity);
transaction.Commit();
}
}
public void Update<T>(T entity) where T : Entity
{
using (var transaction = _session.BeginTransaction())
{
_session.Update(entity);
transaction.Commit();
}
}
}
Solved the Problem by using entity Framework core
You can try .NET Standard implementation of an SQLite wrapper, there are two differences:
Provide a .NET Standard library that sits across the three main platforms: UWP, Android, and iOS with no platform specific libraries.
Provide flat table access with SQL queries. I.e. not being forced to use language integrated ORM like functionality.
https://github.com/MelbourneDeveloper/SQLite.Net.Standard
Related
I have a couple of simple services that exchange information:
public class Service2: PPlusAppServiceBase
{
private readonly IAbpSession _session;
public Service2(IAbpSession session)
{
_session = session;
}
public Entity getEntity()
{
Entity et = new Entity();
Service1 _service1 = new Service1(_session);
[...]
_service1.getEntity();
[...]
return et;
}
}
public class Service1: PPlusAppServiceBase
{
private readonly IAbpSession _session;
public Service1(IAbpSession session)
{
_session = session;
}
public Entity getEntity()
{
_session.[...]
return et;
}
}
OK, it works properly and I just have to send the session to work. But how can I do it simply when I have to get information from a more complex service? Like the default Boilerplate? For example, EditionAppService:
public class EditionAppService : PPlusAppServiceBase, IEditionAppService
{
private readonly EditionManager _editionManager;
private readonly IRepository<SubscribableEdition> _editionRepository;
private readonly IRepository<Tenant> _tenantRepository;
private readonly IBackgroundJobManager _backgroundJobManager;
public EditionAppService(
EditionManager editionManager,
IRepository<SubscribableEdition> editionRepository,
IRepository<Tenant> tenantRepository,
IBackgroundJobManager backgroundJobManager)
{
_editionManager = editionManager;
_editionRepository = editionRepository;
_tenantRepository = tenantRepository;
_backgroundJobManager = backgroundJobManager;
}
[AbpAuthorize(AppPermissions.Pages_Editions)]
public async Task<ListResultDto<EditionListDto>> GetEditions()
{
var editions = await (from edition in _editionRepository.GetAll()
join expiringEdition in _editionRepository.GetAll() on edition.ExpiringEditionId equals expiringEdition.Id into expiringEditionJoined
from expiringEdition in expiringEditionJoined.DefaultIfEmpty()
select new
{
Edition = edition,
expiringEditionDisplayName = expiringEdition.DisplayName
}).ToListAsync();
var result = new List<EditionListDto>();
foreach (var edition in editions)
{
var resultEdition = ObjectMapper.Map<EditionListDto>(edition.Edition);
resultEdition.ExpiringEditionDisplayName = edition.expiringEditionDisplayName;
result.Add(resultEdition);
}
return new ListResultDto<EditionListDto>(result);
}
}
As you can see, the constructor is more complex, the constructor data comes directly defined by swagger (ASP.NET Boilerplate creates dynamic drivers and swagger, and it is these that carry this data that they use as a builder), but when making the call from another service I can't get them.
What is the best way to do that is edit the minimum the second?
In Service2, where I have to call EditionAppService.GetEditions I need something like:
EditionAppService _editionAppService = new EditionAppService();
_editionAppService.GetEditions().Result;
But wait for the builder I don't have
That design pattern is called Dependency Injection.
Do this instead:
public class Service2: PPlusAppServiceBase
{
private readonly EditionAppService _editionAppService; // Add this
private readonly Service1 _service1; // Add this
private readonly IAbpSession _session;
public Service2(
EditionAppService editionAppService, // Add this
Service1 service1, // Add this
IAbpSession session)
{
_editionAppService = editionAppService; // Add this
_service1 = service1; // Add this
_session = session;
}
public Entity getEntity()
{
Entity et = new Entity();
// Service1 _service1 = new Service1(_session); // Remove this
// ...
_service1.getEntity();
// ...
return et;
}
// ...
}
Related: Should I be calling an AppService from another AppService?
Using EF Core (or any ORM for that matter) I want to keep track of the number of queries the ORM makes to the database during some operation in my software.
I've used SQLAlchemy under Python earlier, and on that stack this is faily easy to set up. I typically have unit tests that assert on the number of queries made for a scenario, against an in-memory SQLite database.
Now I want to do the same thing using EF Core, and have looked at the Logging documentation.
In my test setup code I do as the documentation says:
using (var db = new BloggingContext())
{
var serviceProvider = db.GetInfrastructure<IServiceProvider>();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddProvider(new MyLoggerProvider());
}
But I run into problems that I suspect are the results of the following (also from the docs):
You only need to register the logger with a single context instance.
Once you have registered it, it will be used for all other instances
of the context in the same AppDomain.
The problems I see in my tests indicates that my logger implementation is shared across multiple contexts (this is in accordance with the docs as I read them). And since a) my test runner runs tests in parallell and b) my entire test suite creates hundreds of db contexts - it does not work very well.
Question/issues:
Is what I want possible?
I.e. can I register a logger with a db context that is only used for that db context instance?
Are there other ways to accomplish what I am trying to do?
Call DbContextOptionsBuilder.UseLoggerFactory(loggerFactory) method to log all SQL output of a particular context instance. You could inject a logger factory in the context's constructor.
Here is a usage example:
//this context writes SQL to any logs and to ReSharper test output window
using (var context = new TestContext(_loggerFactory))
{
var customers = context.Customer.ToList();
}
//this context doesn't
using (var context = new TestContext())
{
var products = context.Product.ToList();
}
Generally, I use this feature for manual testing. To keep the original context class clean, a derived testable context is declared with overridden OnConfiguring method:
public class TestContext : FooContext
{
private readonly ILoggerFactory _loggerFactory;
public TestContext() { }
public TestContext(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(_loggerFactory);
}
}
It's enough to log SQL queries. Don't forget to attach a suitable logger (like Console) to loggerFactory before you pass it to context.
Part II: Pass logs to xUnit output and ReSharper test output window
We can create a loggerFactory in a test class constructor:
public class TestContext_SmokeTests : BaseTest
{
public TestContext_SmokeTests(ITestOutputHelper output)
: base(output)
{
var serviceProvider = new ServiceCollection().AddLogging().BuildServiceProvider();
_loggerFactory = serviceProvider.GetService<ILoggerFactory>();
_loggerFactory.AddProvider(new XUnitLoggerProvider(this));
}
private readonly ILoggerFactory _loggerFactory;
}
The test class is derived from BaseTest which supports the writing to xUnit output:
public interface IWriter
{
void WriteLine(string str);
}
public class BaseTest : IWriter
{
public ITestOutputHelper Output { get; }
public BaseTest(ITestOutputHelper output)
{
Output = output;
}
public void WriteLine(string str)
{
Output.WriteLine(str ?? Environment.NewLine);
}
}
The most tricky part is to implement a logging provider accepting IWriter as a parameter:
public class XUnitLoggerProvider : ILoggerProvider
{
public IWriter Writer { get; private set; }
public XUnitLoggerProvider(IWriter writer)
{
Writer = writer;
}
public void Dispose()
{
}
public ILogger CreateLogger(string categoryName)
{
return new XUnitLogger(Writer);
}
public class XUnitLogger : ILogger
{
public IWriter Writer { get; }
public XUnitLogger(IWriter writer)
{
Writer = writer;
Name = nameof(XUnitLogger);
}
public string Name { get; set; }
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
if (!this.IsEnabled(logLevel))
return;
if (formatter == null)
throw new ArgumentNullException(nameof(formatter));
string message = formatter(state, exception);
if (string.IsNullOrEmpty(message) && exception == null)
return;
string line = $"{logLevel}: {this.Name}: {message}";
Writer.WriteLine(line);
if (exception != null)
Writer.WriteLine(exception.ToString());
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return new XUnitScope();
}
}
public class XUnitScope : IDisposable
{
public void Dispose()
{
}
}
}
We've done here! All the SQL logs will be shown in Rider/Resharper test output window.
For EF Core 5.0 Simple Logging (what a name!) was introduced
EF Core logs can be accessed from any type of application through the use of LogTo when configuring a DbContext instance. This configuration is commonly done in an override of DbContext.OnConfiguring. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Alternately, LogTo can be called as part of AddDbContext or when creating a DbContextOptions instance to pass to the DbContext constructor.
Writing to a file. But I'd rather inject some kind of logger into db context and use it instead of writing logging logic inside of context.
private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(_logStream.WriteLine);
public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}
Very simply,
Install this Nuget package => Microsoft.Extensions.Logging.Console
(right click on your project=> Manage Nuget packages => then look for it)
(or on this link https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console/ )
then Rebuild the project
// then your db context have to look like this =>
public class Db : DbContext
{
public readonly ILoggerFactory MyLoggerFactory;
public Db()
{
MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(MyLoggerFactory);
}
}
Result =>
Read this:
learn.microsoft.com/en-us/ef/core/miscellaneous/logging
It is very important that applications do not create a new
ILoggerFactory instance for each context instance. Doing so will
result in a memory leak and poor performance.1
If you want to log to static destionation (e.g. console) Ilja's answer works, but if you want to log first to custom buffers, when each dbContext collects log messages to its own buffer (and that what you would like to do in multiuser service), then UPSSS - memory leaks (and memory leak is about 20 mb per almost empty model)...
When EF6 had simple solution to subscribe to an Log event in one line, now to inject your logging this way:
var messages = new List<string>();
Action<string> verbose = (text) => {
messages.Add(text);
}; // add logging message to buffer
using (var dbContext = new MyDbContext(BuildOptionsBuilder(connectionString, inMemory), verbose))
{
//..
};
you should write the pooling monster.
P.S. Somebody tell to Ef Core architects that they have wrong understanding of DI and those fancy service locators that they call "containers" and fluent UseXXX that they borrow from ASP.Core can't replace "vulgar DI from constructor"! At least log function should be normally injectable through constructor.
*P.P.S. Read also this https://github.com/aspnet/EntityFrameworkCore/issues/10420 . This means that adding LoggerFactory broke access to InMemory data provider. This is an Abstraction Leak as it is. EF Core has problems with architecture.
ILoggerFactory pooling code:
public class StatefullLoggerFactoryPool
{
public static readonly StatefullLoggerFactoryPool Instance = new StatefullLoggerFactoryPool(()=> new StatefullLoggerFactory());
private readonly Func<StatefullLoggerFactory> construct;
private readonly ConcurrentBag<StatefullLoggerFactory> bag = new ConcurrentBag<StatefullLoggerFactory>();
private StatefullLoggerFactoryPool(Func<StatefullLoggerFactory> construct) =>
this.construct = construct;
public StatefullLoggerFactory Get(Action<string> verbose, LoggerProviderConfiguration loggerProviderConfiguration)
{
if (!bag.TryTake(out StatefullLoggerFactory statefullLoggerFactory))
statefullLoggerFactory = construct();
statefullLoggerFactory.LoggerProvider.Set(verbose, loggerProviderConfiguration);
return statefullLoggerFactory;
}
public void Return(StatefullLoggerFactory statefullLoggerFactory)
{
statefullLoggerFactory.LoggerProvider.Set(null, null);
bag.Add(statefullLoggerFactory);
}
}
public class StatefullLoggerFactory : LoggerFactory
{
public readonly StatefullLoggerProvider LoggerProvider;
internal StatefullLoggerFactory() : this(new StatefullLoggerProvider()){}
private StatefullLoggerFactory(StatefullLoggerProvider loggerProvider) : base(new[] { loggerProvider }) =>
LoggerProvider = loggerProvider;
}
public class StatefullLoggerProvider : ILoggerProvider
{
internal LoggerProviderConfiguration loggerProviderConfiguration;
internal Action<string> verbose;
internal StatefullLoggerProvider() {}
internal void Set(Action<string> verbose, LoggerProviderConfiguration loggerProviderConfiguration)
{
this.verbose = verbose;
this.loggerProviderConfiguration = loggerProviderConfiguration;
}
public ILogger CreateLogger(string categoryName) =>
new Logger(categoryName, this);
void IDisposable.Dispose(){}
}
public class MyDbContext : DbContext
{
readonly Action<DbContextOptionsBuilder> buildOptionsBuilder;
readonly Action<string> verbose;
public MyDbContext(Action<DbContextOptionsBuilder> buildOptionsBuilder, Action<string> verbose=null): base()
{
this.buildOptionsBuilder = buildOptionsBuilder;
this.verbose = verbose;
}
private Action returnLoggerFactory;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (verbose != null)
{
var loggerFactory = StatefullLoggerFactoryPool.Instance.Get(verbose, new LoggerProviderConfiguration { Enabled = true, CommandBuilderOnly = false });
returnLoggerFactory = () => StatefullLoggerFactoryPool.Instance.Return(loggerFactory);
optionsBuilder.UseLoggerFactory(loggerFactory);
}
buildOptionsBuilder(optionsBuilder);
}
// NOTE: not threadsafe way of disposing
public override void Dispose()
{
returnLoggerFactory?.Invoke();
returnLoggerFactory = null;
base.Dispose();
}
}
private static Action<DbContextOptionsBuilder> BuildOptionsBuilder(string connectionString, bool inMemory)
{
return (optionsBuilder) =>
{
if (inMemory)
optionsBuilder.UseInMemoryDatabase(
"EfCore_NETFramework_Sandbox"
);
else
//Assembly.GetAssembly(typeof(Program))
optionsBuilder.UseSqlServer(
connectionString,
sqlServerDbContextOptionsBuilder => sqlServerDbContextOptionsBuilder.MigrationsAssembly("EfCore.NETFramework.Sandbox")
);
};
}
class Logger : ILogger
{
readonly string categoryName;
readonly StatefullLoggerProvider statefullLoggerProvider;
public Logger(string categoryName, StatefullLoggerProvider statefullLoggerProvider)
{
this.categoryName = categoryName;
this.statefullLoggerProvider = statefullLoggerProvider;
}
public IDisposable BeginScope<TState>(TState state) =>
null;
public bool IsEnabled(LogLevel logLevel) =>
statefullLoggerProvider?.verbose != null;
static readonly List<string> events = new List<string> {
"Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosing",
"Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosed",
"Microsoft.EntityFrameworkCore.Database.Command.DataReaderDisposing",
"Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpened",
"Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpening",
"Microsoft.EntityFrameworkCore.Infrastructure.ServiceProviderCreated",
"Microsoft.EntityFrameworkCore.Infrastructure.ContextInitialized"
};
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (statefullLoggerProvider?.verbose != null)
{
if (!statefullLoggerProvider.loggerProviderConfiguration.CommandBuilderOnly ||
(statefullLoggerProvider.loggerProviderConfiguration.CommandBuilderOnly && events.Contains(eventId.Name) ))
{
var text = formatter(state, exception);
statefullLoggerProvider.verbose($"MESSAGE; categoryName={categoryName} eventId={eventId} logLevel={logLevel}" + Environment.NewLine + text);
}
}
}
}
You can use a bounded context. I used EF Coed first to create two different contexts
Customer bounded context will not log any queries
public class CustomerModelDataContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<PostalCode> PostalCodes { get; set; }
public CustomerModelDataContext()
: base("ConnectionName")
{
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Database.SetInitializer<CustomerModelDataContext>(new Initializer<CustomerModelDataContext>());
//Database.Log = message => DBLog.WriteLine(message);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
API bounded context will log the queries
public class ApiModelDataContext : DbContext
{
public DbSet<ApiToken> ApiTokens { get; set; }
public DbSet<ApiClient> ApiClients { get; set; }
public DbSet<ApiApplication> ApiApplications { get; set; }
public ApiModelDataContext()
: base("ConnectionName")
{
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Database.SetInitializer<ApiModelDataContext>(new Initializer<ApiModelDataContext>());
Database.Log = message => DBLog.WriteLine(message);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
this will log the query to debug output window in VS
public static class DBLog
{
public static void WriteLine(string message)
{
Debug.WriteLine(message);
}
}
In my project ,I used repository and used dependency injection (Autofac Mvc).
My IReposiroty =
public interface IRepository<T> where T:class
{
IEnumerable<T> GetAll();
T GetById(int id);
T Get(Expression<Func<T,bool>> expression);
IQueryable<T> GetMany(Expression<Func<T, bool>> expression);
bool Insert(T obj);
bool Update(T obj);
bool Delete(int id);
int Count();
bool Save();
}
My IPropertyOptionLangRepository
public interface IPropertyOptionLangRepository : IRepository<PropertyOptionLang>
{
}
My PropertyOptionRepository (Just Insert and Save Methods)
public bool Insert(PropertyOptionLang obj)
{
try
{
_database.PropertyOptionLang.Add(obj);
var num=_database.SaveChanges();
return true;
}
catch(Exception e)
{
Console.WriteLine(e);
return false;
}
}
public bool Save()
{
try
{
_database.SaveChanges();
return true;
}
catch
{
return false;
}
}
And My Controller ( constructor and Insert Method)
private readonly IPropertyOptionRepository _propertyOptionRepository ;
private readonly IPropertyOptionLangRepository _propertyOptionLangRepository ;
private readonly ILanguageRepository _languageRepository;
public FieldController(IPropertyOptionRepository propertyOptionRepository,
IPropertyOptionLangRepository propertyOptionLangRepository,
ILanguageRepository languageRepository)
{
_languageRepository = languageRepository;
_propertyOptionRepository = propertyOptionRepository;
_propertyOptionLangRepository = propertyOptionLangRepository;
}
public ActionResult Add()
{
PropertyOptionLang test3 = new PropertyOptionLang();
var option = _propertyOptionRepository.GetById(2);
var lang2 = _languageRepository.GetById(2);
test3.Language = lang2;
test3.PropertyOption = option;
test3.Name = "hasan";
test3.Prefix = "test2";
test3.Value = "aaa";
_propertyOptionLangRepository.Insert(test3);
_propertyOptionLangRepository.Save();
}
exception message is : " e.Message "An entity object cannot be referenced by multiple instances of IEntityChangeTracker."
thanks for help..
Note: I search already for this exception message but I failed again
Edit: For Autofac configurate I created a new class and update global.asax for startup. `public static class Bootstrapper
{
public static void RunConfig()
{
BuildAutofac();
}
private static void BuildAutofac()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<LanguageRepository>().As<ILanguageRepository>();
builder.RegisterType<PropertyOptionLangRepository>().As<IPropertyOptionLangRepository>();
builder.RegisterType<PropertyOptionRepository>().As<IPropertyOptionRepository>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}`
I have a problem with save more than one items to database in c# asp.net with fluent nhibernate. I have :
public static readonly ISessionFactory SessionFactory = DbContext.SessionFactory();
public static readonly ISession Session = SessionFactory.OpenSession();
public static readonly ITransaction Transaction = Session.BeginTransaction();
public IEnumerable<Candidate> Candidates => Session.Query<Candidate>();
public Candidate GetUser(int id)
{
return Session.Query<Candidate>().FirstOrDefault(x => x.Id == id);
}
public void AddCandidate(Candidate candidate)
{
try
{
Session.Save(candidate);
Transaction.Commit();
}
catch (Exception exception)
{
throw;
}
}
And the error is : IsolationLevel = '((NHibernate.Transaction.AdoTransaction)Transaction).IsolationLevel' threw an exception of type 'System.NullReferenceException'
More information : can't access the deleted object
My Class DbContext:
public static class DbContext
{
private static ISessionFactory _sessionFactory;
static DbContext()
{
GetFactory();
}
public static void GetFactory()
{
var myEntities = new[]
{
typeof (ApplicationUser)
};
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("ConnectionString"))
.ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(x =>
{
x.SetProperty("", "");
x.AddDeserializedMapping(MappingHelper.GetIdentityMappings(myEntities), null);
})
.ExposeConfiguration(BuildSchema);
_sessionFactory = configuration.BuildSessionFactory();
}
public static ISessionFactory SessionFactory()
{
return _sessionFactory;
}
public static ISession GetSession()
{
if (!CurrentSessionContext.HasBind(_sessionFactory))
CurrentSessionContext.Bind(_sessionFactory.OpenSession());
return _sessionFactory.GetCurrentSession();
}
public static ISession MakeSession()
{
return _sessionFactory.OpenSession();
}
//public static IUserStore<ApplicationUser, string> Users => new IdentityStore(MakeSession());
private static void BuildSchema(Configuration config)
{
new SchemaUpdate(config)
.Execute(true, true);
}
}
Please, help!
Thanks!
SessionFactory should be singleton and using it as static can be part of the solution. Objects from ISession and ITransaction should be created when necessary. Try change your code to something like this:
public static readonly ISessionFactory SessionFactory = DbContext.SessionFactory();
public Candidate GetUser(int id)
{
Candidate candidate = null;
using (var session = SessionFactory.OpenSession())
{
candidate = Session.Query<Candidate>().FirstOrDefault(x => x.Id == id);
}
return candidate;
}
public void AddCandidate(Candidate candidate)
{
using (var session = SessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
Session.Save(candidate);
transaction.Commit();
}
catch
{
transaction.RollBack();
throw;
}
}
}
}
Avoid to expose Queryable as your code is doing on the Candidates property.
There are some approaches to rightly implement the session management on an application and depending whant environment code is executed it can change.
I recommend you to see these posts:
http://benfoster.io/blog/yet-another-session-per-request-post
How to implement session-per-request pattern in asp.net mvc with Nhibernate
I get a weird behavior with NHibernate with Fluent Configuration.
Whenever a generic exception unrelated to the NHibernate occurs i.e. in the view a DivideByZeroException every request after the exception throws.
An exception of type 'NHibernate.LazyInitializationException' occurred in NHibernate.dll but was not handled in user code. Additional information: Initializing[Entity]-Could not initialize proxy - no Session.
Due to nature of the bug the bug is critical due to the fact that 1 user can make the whole website dead if he generates an exception
Following it is my HttpModule for Nhibernate with Asp.Net MVC 5 that takes care of sessions.
NHibernateSessionPerRequest.cs
public class NHibernateSessionPerRequest : IHttpModule
{
private static readonly ISessionFactory SessionFactory;
// Constructs our HTTP module
static NHibernateSessionPerRequest()
{
SessionFactory = CreateSessionFactory();
}
// Initializes the HTTP module
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
// Disposes the HTTP module
public void Dispose() { }
// Returns the current session
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
// Opens the session, begins the transaction, and binds the session
private static void BeginRequest(object sender, EventArgs e)
{
ISession session = SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
// Unbinds the session, commits the transaction, and closes the session
private static void EndRequest(object sender, EventArgs e)
{
ISession session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
throw;
}
finally
{
session.Close();
session.Dispose();
}
}
// Returns our session factory
private static ISessionFactory CreateSessionFactory()
{
if (HttpContext.Current != null) //for the web apps
_configFile = HttpContext.Current.Server.MapPath(
string.Format("~/App_Data/{0}", CacheFile)
);
_configuration = LoadConfigurationFromFile();
if (_configuration == null)
{
FluentlyConfigure();
SaveConfigurationToFile(_configuration);
}
if (_configuration != null) return _configuration.BuildSessionFactory();
return null;
}
// Returns our database configuration
private static MsSqlConfiguration CreateDbConfigDebug2()
{
return MsSqlConfiguration
.MsSql2008
.ConnectionString(c => c.FromConnectionStringWithKey("MyConnection"));
}
// Updates the database schema if there are any changes to the model,
// or drops and creates it if it doesn't exist
private static void UpdateSchema(Configuration cfg)
{
new SchemaUpdate(cfg)
.Execute(false, true);
}
private static void SaveConfigurationToFile(Configuration configuration)
{
using (var file = File.Open(_configFile, FileMode.Create))
{
var bf = new BinaryFormatter();
bf.Serialize(file, configuration);
}
}
private static Configuration LoadConfigurationFromFile()
{
if (IsConfigurationFileValid == false)
return null;
try
{
using (var file = File.Open(_configFile, FileMode.Open))
{
var bf = new BinaryFormatter();
return bf.Deserialize(file) as Configuration;
}
}
catch (Exception)
{
return null;
}
}
private static void FluentlyConfigure()
{
if (_configuration == null)
{
_configuration = Fluently.Configure()
.Database(CreateDbConfigDebug2)
.CurrentSessionContext<WebSessionContext>()
.Cache(c => c.ProviderClass<SysCacheProvider>().UseQueryCache())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<EntityMap>()
.Conventions.Add(DefaultCascade.All(), DefaultLazy.Always()))
.ExposeConfiguration(UpdateSchema)
.ExposeConfiguration(c => c.Properties.Add("cache.use_second_level_cache", "true"))
.BuildConfiguration();
}
}
private static bool IsConfigurationFileValid
{
get
{
var ass = Assembly.GetAssembly(typeof(EntityMap));
var configInfo = new FileInfo(_configFile);
var assInfo = new FileInfo(ass.Location);
return configInfo.LastWriteTime >= assInfo.LastWriteTime;
}
}
private static Configuration _configuration;
private static string _configFile;
private const string CacheFile = "hibernate.cfg.xml";
}
Edit
The Repository Implementation i use
public class Repository<T> : IIntKeyedRepository<T> where T : class
{
private readonly ISession _session;
public Repository()
{
_session = NHibernateSessionPerRequest.GetCurrentSession();
}
#region IRepository<T> Members
public bool Add(T entity)
{
_session.Save(entity);
return true;
}
public bool Add(System.Collections.Generic.IEnumerable<T> items)
{
foreach (T item in items)
{
_session.Save(item);
}
return true;
}
public bool Update(T entity)
{
_session.Update(entity);
return true;
}
public bool Delete(T entity)
{
_session.Delete(entity);
return true;
}
public bool Delete(System.Collections.Generic.IEnumerable<T> entities)
{
foreach (T entity in entities)
{
_session.Delete(entity);
}
return true;
}
#endregion
#region IIntKeyedRepository<T> Members
public T FindBy(int id)
{
return _session.Get<T>(id);
}
#endregion
#region IReadOnlyRepository<T> Members
public IQueryable<T> All()
{
return _session.Query<T>();
}
public T FindBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return FilterBy(expression).Single();
}
public IQueryable<T> FilterBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return All().Where(expression).AsQueryable();
}
#endregion
}
Edit 2
The base controller class I use
public class BaseController : Controller
{
private readonly IRepository<UserEntity> _userRepository;
public BaseController()
{
_userRepository = new Repository<UserEntity>();
BaseModel = new LayoutModel {Modals = new List<string>()};
}
public UserEntity LoggedUser { get; set; }
public LayoutModel BaseModel { get; set; }
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
base.OnActionExecuting(ctx);
if (HttpContext.User.Identity.IsAuthenticated)
{
if (Session != null && Session["User"] != null)
{
LoggedUser = (User) Session["User"];
}
var curUsername = HttpContext.User.Identity.Name;
if (LoggedUser == null || LoggedUser.Entity2.un!= curUsername)
{
LoggedUser = _userRepository.FindBy(u => u.Entity2.un== curUsername);
Session["User"] = LoggedUser;
}
BaseModel.LoggedUser = LoggedUser;
BaseModel.Authenticated = true;
}
else
{
LoggedUser = new UserEntity
{
Entity= new Entity{un= "Guest"},
};
BaseModel.LoggedUser = LoggedUser;
}
}
}
The extended question and all the snippets - are finally helping to find out where is the issue.
There is a really big issue: Session["User"] = LoggedUser;
This would hardly work. Why?
because we place into long running object (Web Session)
an instance loaded via very shortly lasting Web Request
Not all its properties will/could be loaded, When we place LoggedUser into session. It could be just a root entity with many proxies representing references and collections. These will NEVER be loaded later, because its Mather session is closed... gone
Solution?
I would use .Clone() of the User object. In its implementation we can explicitly load all needed references and collections and clone them as well. Such object could be placed into the Web Session
[Serializable]
public class User, ICloneable, ...
{
...
public override object Clone()
{
var entity = base.Clone() as User;
entity.Role = Role.Clone() as Role;
...
return entity;
}
So, what would be placed into session?
Session["User"] = LoggedUser.Clone();
As Radim Köhler noted i was saving a lazy-loaded object in Session that caused the problem.
But i wanted to avoid the Serilization of all objects and i fixed it as follows.
I added the following method to eager-load an entity instead of lazy
public T FindByEager(int id)
{
T entity = FindBy(id);
NHibernateUtil.Initialize(entity);
return entity;
}
And changed BaseController to
if (Session != null) Session["User"] = userRepository.FindByEager(LoggedUser.Id);