asp.net web api 2 LINQ to Entities does not recognize the method - asp.net

I want to invoke method durning mapping my domain class to DTO class but after many tries with LINQ to Entities or LINQ to objects i have failed and i'm getting weird different errors. Actulal error is just a "LINQ to Entities does not recognize the method 'System.String ResizeToLogoImage(System.String)' method, and this method cannot be translated into a store expression.".
Mapping method:
public async Task<SingleCategory> SingleCategoryMapping(EventerApiContext context, int id)
{
var category = await context.Category.Select(c => new SingleCategory
{
CategoryId = c.CategoryId,
CategoryName = c.CategoryName,
CityId = c.CityId,
Events = context.Event.ToList().Where(e=>e.CategoryId == id).Select(e=> new EventForSingleCategory
{
EventId = e.EventId,
EventName = e.EventName,
EventLogo = ImageProcessor.ResizeToLogoImage(e.EventDetail.EventImage.EventImageBase64)
}).ToList()
}).SingleOrDefaultAsync(c => c.CategoryId == id);
return category;
}
Method to be invoked.
public static string ResizeToLogoImage(string base64String)
{
if (base64String == null)
{
return "NULL";
}
var imageToResize = Base64ToImage(base64String);
var resizedImage = ScaleImage(imageToResize, 50, 50);
return ImageToBase64(resizedImage, imageToResize.RawFormat);
}
I know error is appearing during EventLogo property mapping but i have no more idea what to do.

Try to get the data first, before you do the Select statement. I suspect that it is trying to execute ResizeToLogoImage on the database :)

Related

How to "SET IDENTITY_INSERT ON" on Entity Framework [duplicate]

This question already has an answer here:
Entity Framework: Cannot insert explicit value for identity column in table '[table]' when IDENTITY_INSERT is set to OFF
(1 answer)
Closed 8 months ago.
I made a few tables in EF and entered in some seed data where I give value to a few columns with a primary key. When I run the application I am getting the error message:
Cannot insert explicit value for identity column in table 'Persons' when IDENTITY_INSERT is set to OFF.
How do I turn it on? I read on here to use:
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
above the property that is a primary key. I am still getting the same error message unfortunately. Please help.
I added [DatabaseGenerated(DatabaseGeneratedOption.None)] to all my properties that have a primary key. When I ran the migration I can see that the identity column is removed, But I am still getting the same error message.
When I go into SQL SEO I can still see the identity column on my primary key. I tried refreshing the database. What am I doing wrong? The only thing I can do is go into properties and remove the identity, but why can't I do it the way mentioned above?
In EF Core 1.1.2, I got this to work with transactions. In my "database initializer" that put seed data into the tables. I used the technique from this EF6 answer. Here's a sample of the code:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Had to deal with the same issue and this seems to be a clean solution.
Credit to >> https://github.com/dotnet/efcore/issues/11586
I have made some changes so it now works with .Net Core 3.1 + (Tested in .Net 5) and also added this Method SaveChangesWithIdentityInsert
public static class IdentityHelpers
{
public static Task EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: true);
public static Task DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: false);
private static Task SetIdentityInsert<T>(DbContext context, bool enable)
{
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
return context.Database.ExecuteSqlRawAsync(
$"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>(this DbContext context)
{
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
}
Usage
var data = new MyType{SomeProp= DateTime.Now, Id = 1};
context.MyType.Add(data);
context.SaveChangesWithIdentityInsert<MyType>();
Improved solution based on NinjaCross' answer.
This code is added directly in the database context class and allows to save changes by also specifying that identity insert is needed for a certain type (mapped to a table).
Currently, I have only used this for integrative testing.
public async Task<int> SaveChangesWithIdentityInsertAsync<TEnt>(CancellationToken token = default)
{
await using var transaction = await Database.BeginTransactionAsync(token);
await SetIdentityInsertAsync<TEnt>(true, token);
int ret = await SaveChangesExAsync(token);
await SetIdentityInsertAsync<TEnt>(false, token);
await transaction.CommitAsync(token);
return ret;
}
private async Task SetIdentityInsertAsync<TEnt>(bool enable, CancellationToken token)
{
var entityType = Model.FindEntityType(typeof(TEnt));
var value = enable ? "ON" : "OFF";
string query = $"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}";
await Database.ExecuteSqlRawAsync(query, token);
}
Steve Nyholm's answer works fine, but I will provide some extra explanation and some generic code with exception handling.
Normally the context takes care of the transaction, but in this case manually taking care of it is required. Why?
Database context will generate a BEGIN TRAN after the SET IDENTITY_INSERT is issued. This will make transaction's inserts to fail since IDENTITY_INSERT seems to affect tables at session/transaction level.
So, everything must be wrapped in a single transaction to work properly.
Here is some useful code to seed at key level (as opposed to table level):
Extensions.cs
[Pure]
public static bool Exists<T>(this DbSet<T> dbSet, params object[] keyValues) where T : class
{
return dbSet.Find(keyValues) != null;
}
public static void AddIfNotExists<T>(this DbSet<T> dbSet, T entity, params object[] keyValues) where T: class
{
if (!dbSet.Exists(keyValues))
dbSet.Add(entity);
}
DbInitializer.cs
(assumes that model class name is the same as table name)
private static void ExecuteWithIdentityInsertRemoval<TModel>(AspCoreTestContext context, Action<AspCoreTestContext> act) where TModel: class
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TModel).Name + " ON;");
context.SaveChanges();
act(context);
context.SaveChanges();
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
throw;
}
finally
{
context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TModel).Name + " OFF;");
context.SaveChanges();
}
}
}
public static void Seed(AspCoreTestContext context)
{
ExecuteWithIdentityInsertRemoval<TestModel>(context, ctx =>
{
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 1, ModelCode = "Test model #1" }, 1);
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 2, ModelCode = "Test model #2" }, 2);
});
}
The solution proposed by #sanm2009 contains some nice ideas.
However the implementation has some imperfections related to the misusage of Task/async/await.
The method SaveChangesWithIdentityInsert does not return Task, nor await for the calls to EnableIdentityInsert and DisableIdentityInsert.
This could lead to undesired side effects.
The following implementations supports both async/await, and non-awaitable paradigms.
#region IDENTITY_INSERT
public static void EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, true);
public static void DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, false);
private static void SetIdentityInsert<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
#endregion
#region IDENTITY_INSERT ASYNC
public static async Task EnableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, true);
public static async Task DisableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, false);
private static async Task SetIdentityInsertAsync<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
await context.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static async Task SaveChangesWithIdentityInsertAsync<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
await using var transaction = await context.Database.BeginTransactionAsync();
await context.EnableIdentityInsertAsync<T>();
await context.SaveChangesAsync();
await context.DisableIdentityInsertAsync<T>();
await transaction.CommitAsync();
}
#endregion
#Steve Nyholm answer is OK, But in .Net core 3 ExecuteSqlCommand is Obsolete, ExecuteSqlInterpolated replacement of ExecuteSqlCommand:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Another way is to explicitly open a connection then SET IDENTITY_INSERT <table> ON.
var conn = context.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
conn.Open();
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Posts ON");
var post = new WeblogPost()
{
Id= oldPost.Pk, // <!--- explicit value to Id field
Title = oldPost.Title,
...
};
context.Posts.Add(post);
conn.Close();
Apparently once a connection has been explicitly opened before an EF request, that connection is not automatically closed by EF, so the setting is applied to the same connection context.
This is the same reason that Steve's response with transactions works as transactions keep a connection alive.
Note: you don't want to put the connection into a using statement if you plan to use the same context again later in the application/request. The connection has to exist, so the best way to clear the connection context is to .Close() it, thereby returning EF to its default behavior of opening and closing the connection per operation.
Below solution worked for me.(Link)
I have added below annotations. and removed [Key] Annotation.
[KeyAttribute()]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
Namespace can be changed according to the entity framework version. For Entity framework core namespace is System.ComponentModel.DataAnnotations.Schema
I did not face a data migration since I have tried in a new project.
Another way is to use ExecuteSqlRaw. Unlike ExecuteSqlInterpolated, you do not have to convert your passed string to a formattable string type.
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users ON");
db.SaveChanges();
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
In order to add related entities with an object graph using the DbContext I used a DbCommandInterceptor which automatically sets INSERT_IDENTITY ON for the table in question and then OFF after the insert. This works with IDs manually set and DbContext.SaveChanges. I used it in my integration tests but after a performance optimization maybe it could be suitable for production code in some cases. Here is my answer to a similar SO question which explains the details.
Use "SET IDENTITY_INSERT [table] ON/OFF" into transaction
public static void TranslateDatabase(ref BDVContext bdvContext)
{
bdvContext.Foro.RemoveRange(bdvContext.Foro);
bdvContext.SaveChanges();
using (var transaction = bdvContext.Database.BeginTransaction())
{
bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] On");
using (old_balsaContext db = new old_balsaContext())
{
long id = 0;
foreach (ForoA77 post in db.ForoA77.Where(x => x.Fecha > new DateTime(2000,1,1) & x.IdPadre == 0 ) )
{
bdvContext.Foro.Add(new Foro
{
Id = ++id
, ParentId = 0
, EditId = 0
, IdDomains = 2
, UserNick = post.IdUsuario == 1 ? bdvContext.Users.Where(x => x.Id == 2).Single().User : post.Nick?? ""
, IdUsers = post.IdUsuario == 1 ? (int?)2 : null
, Title = post.Asunto?? ""
, Text = post.Texto?? ""
, Closed = post.Cerrado?? false
, Banned = post.Veto?? false
, Remarqued = post.Remarcado?? false
, Deleted = false
, Date = post.Fecha?? new DateTime(2001,1,1)
});
}
}
bdvContext.SaveChanges();
bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] Off");
transaction.Commit();
}
}
Note, my entityframework was generated by reverse engineering
If you don't want to use EF core's auto-generating primary key values feature, you can turn it off. You can add your data to the primary key It should resolve the error - Set Identity Insert off
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int StudentId { get; set; }
Setting Database Generation option to None helped me. You can find more about it here- https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations
You should keep it off, dont turn it on !
Its turned off for good reasons (security performance)...
Do this instead in your code.
For example, outside your default Create controller when you need to _context.add() a new entry in your DB:
object mytable = new Mytable
{
//as in your model but without key field ! (its readonly)
myvar = "something",
myage = 50,
done = somefunctionToRetrieveData(somevar),
date = system.datetime.now(),
universalAnswer = 42
}
_context.Add(mytable);
await _context.SaveChangesAsync();

System.InvalidOperationException while dynamically enumerating and retrieving .NET6 Entity Framework DbContext DbSet<...>s instances' records

The subject runtime error's location is inside the following code. Please advise how to fix this issue.
using (var ctx = new MyDbContext())
{
foreach (var entityInfo in ctx.GetEntitiesInfo())
System.Console.WriteLine($"{entityInfo.Index}. EntityName = {entityInfo.Name}, Records count = {entityInfo.RecordCount}");
}
public static class Extensions
{
public readonly record struct EntityInfo (int Index, string? Name, int RecordCount);
public static IEnumerable<EntityInfo> GetEntitiesInfo(this MyDbContext context)
{
var dbSetProperties = context.GetDbSetProperties();
var dbSets = dbSetProperties.Select(x => x.GetValue(context, null)).ToList();
var index = 0;
foreach (IQueryable? dbSet in dbSets)
{
++index;
// Runtime Error happens on next code line on second iteration
// when index = 2:
//
// System.InvalidOperationException:
// 'There is already an open DataReader associated with
// this Connection which must be closed first
//-
dbSet.Load();
var items = new List<object>();
var enumerator = dbSet?.GetEnumerator();
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
yield return new EntityInfo(index, dbSet?.ElementType?.ToString(), items.Count);
}
}
public static IEnumerable<PropertyInfo> GetDbSetProperties(this MyDbContext context)
{
foreach (var property in context.GetType().GetProperties())
if (property?.PropertyType?.FullName?
.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
yield return property;
}
}
[Update]
Actually I wanted to develop a generic code to get all DbSet(s) of a given DbContext. Here it's based on Ivan Stoev's answer to this topic:
public static IEnumerable<IQueryable<object>> GetDbSets(this MyDbContext context)
{
var dbSetProperties = context.GetDbSetTypeProperties();
return dbSetProperties.Select(x => x.GetValue(context, null))
.Cast<IQueryable<object>>();
}
public static IEnumerable<PropertyInfo> GetDbSetTypeProperties(this MyDbContext context)
{
foreach (var property in context.GetType().GetProperties())
if (property?.PropertyType?.FullName?
.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
yield return property;
}
Please advise if the above code can be even more simplified/streamlined.
[Update 2]
GetDbSets() method can be simplified this way:
public static IEnumerable<IQueryable<object>> GetDbSets(this MyDbContext context)
{
return context
.GetType()
.GetRuntimeProperties()
.Where(x => x.PropertyType?.FullName?.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
.Select(x => x.GetValue(context, null))
.Cast<IQueryable<object>>();
}
but is there any other ways to enumerate all DbSet(s) of a given DbContext?
There are a lot of flaws in that code snippet, but the concrete problem in question is caused by the lack of disposal of the IEnumerator returned by the IEnumerable.GetEnumerator() call.
I know the non generic IEnumerator does not implement IDisposable, but that was a miss, and the generic IEnumerator<T> added later normally implements both IEnumerator and IDisposable.
So you have to either account for that and modify the code similar to this:
var items = new List<object>();
var enumerator = dbSet?.GetEnumerator();
try
{
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
or better let the C# foreach do that for you (writing behind the scenes a code similar to the above):
var items = new List<object>();
foreach (var item in dbSet)
items.Add(item);
Note that DbSet<T> type properties are the equivalent of Set<T>() method and are initialized by EF Core at the DbContext instance creation, so neither they nor their content is null, hence all the ?. operators are not needed.
Also calling the Load method is not needed since all it does is to enumerate the db set without storing items in a list (basically foreach with empty body).
Anyway, both previous code snippets will fix the original issue. But there is even better ways. Since all entities are required to be classes (reference types), each db set can be cast to IQueryable<object>
foreach (IQueryable<object> dbSet in dbSets)
which offers some additional benefits.
First, the original code could be fixed just by adding single using statement:
var items = new List<object>();
using var enumerator = dbSet?.GetEnumerator();
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
It can be replaced with foreach as before
var items = new List<object>();
foreach (var item in dbSet)
items.Add(item);
but now you have access to many Enumerable (and Queryable and EF Core specific) extension methods, so you can replace all the iteration code with simple
var items = dbSet.ToList();
And in case you just need the record count, retrieve it with server side query without loading all the data in memory
var itemsCount = dbSet.Count();
yield return new EntityInfo(index, dbSet.ElementType.ToString(), itemsCount);

Generate GUID for Primary key automatically In EF5

I'm using Guids as primary key for the entities in my database, using the model binding feature of asp.net 4.5 web forms when im inserting a record in the database using entity framework 5 im doing something like
public void onInsert([Control("ControlID")] int? countryID){
if(countryID.hasValue){
var DbEntityToInsert = new DbEntity(); //where DbEntity is the class generated by the EF
TryUpdateModel(DbEntityToInsert);
DbEntityToInsert.GuidPK = Guid.NewGuid();
if(Page.ModelState.IsValid){
using(var db = new DatabaseContext()){
db.Add(DbEntityToInsert);
db.Save();
}//using ends
}//modelstate.isvalid if ends
}//countryid.hasvalue ends
}//main method ends
now i wanted to ask is there a way i can tell EF to generate a Guid for the PK while inserting a new record so i dont have to write the line
DbEntityToInsert.GuidPK = Guid.NewGuid();
You can try to override SaveChanges in your derived context. The main task is to find out if an entity has a GuidPK property as primary key. Here is an attempt using reflection:
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
.Select(e => new
{
Entity = e.Entity,
PropertyInfo = e.Entity.GetType().GetProperty("GuidPK")
})
.Where(x => x.PropertyInfo != null && x.PropertyInfo.CanWrite);
foreach (var x in addedEntities)
x.PropertyInfo.SetValue(x.Entity, Guid.NewGuid());
return base.SaveChanges();
}
To avoid reflection here you could have a common interface that is implemented by all your entities that use a GuidPK property as PK:
public interface IEntityWithGuidPK
{
Guid GuidPK { get; set; }
}
public class DbEntity : IEntityWithGuidPK
{
public Guid GuidPK { get; set; }
// ...
}
Then the code in SaveChanges could be:
//...
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added &&
e.Entity is IEntityWithGuidPK)
.Select(e => e.Entity as IEntityWithGuidPK);
foreach (var e in addedEntities)
e.GuidPK = Guid.NewGuid();
//...

Using one session per request, how to handle updating child objects

I'm having some serious issues with Fluent Nhibernate in my ASP.NET WebForms app when trying to modify a child object and then saving the parent object.
My solution is currently made of 2 projects :
Core : A class library where all entities & repositories classes are located
Website : The ASP.NET 4.5 WebForms application
Here is my simple mapping for my Employee object:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Username);
Map(x => x.FirstName);
Map(x => x.LastName);
HasMany(x => x.TimeEntries).Inverse().Cascade.All().KeyColumn("Employee_id");
}
}
Here is my my mapping for the TimeEntry object:
public class TimeEntryMap : ClassMap<TimeEntry>
{
public TimeEntryMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Date);
Map(x => x.Length);
References(x => x.Employee).Column("Employee_id").Not.Nullable();
}
}
As stated in the title, i'm using one session per request in my web app, using this code in Gobal.asax:
public static ISessionFactory SessionFactory = Core.SessionFactoryManager.CreateSessionFactory();
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected Global()
{
BeginRequest += delegate
{
System.Diagnostics.Debug.WriteLine("New Session");
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if (CurrentSession != null)
CurrentSession.Dispose();
};
}
Also, here is my SessionFactoryManager class:
public class SessionFactoryManager
{
public static ISession CurrentSession;
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Website.Properties.Settings.WebSiteConnString")))
.Mappings(m => m
.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
}
public static ISession GetSession()
{
return (ISession)HttpContext.Current.Items["current.session"];
}
}
Here is one of my repository class, the one i use to handle the Employee's object data operations:
public class EmployeeRepository<T> : IRepository<T> where T : Employee
{
private readonly ISession _session;
public EmployeeRepository(ISession session)
{
_session = session;
}
public T GetById(int id)
{
T result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Get<T>(id);
tx.Commit();
}
return result;
}
public IList<T> GetAll()
{
IList<T> result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().ToList();
tx.Commit();
}
return result;
}
public bool Save(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.SaveOrUpdate(item);
tx.Commit();
result = true;
}
return result;
}
public bool Delete(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.Delete(_session.Load(typeof (T), item.Id));
tx.Commit();
result = true;
}
return result;
}
public int Count()
{
var result = 0;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().Count();
tx.Commit();
}
return result;
}
}
Now, here is my problem. When i'm trying to insert Employee(s), everything is fine. Updating is also perfect... well, as long as i'm not updating one of the TimeEntry object referenced in the "TimeEntries" property of Employee...
Here is where an exception is raised (in a ASPX file of the web project):
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
Here is the exception that is raised:
[NonUniqueObjectException: a different object with the same identifier
value was already associated with the session: 1, of entity:
Core.Entities.Employee]
Basically, whenever I try to
Load an employee and
Modify one of the saved TimeEntry, I get that exception.
FYI, I tried replacing the SaveOrUpdate() in the repository for Merge(). It did an excellent job, but when creating an object using Merge(), my object never gets it's Id set.
I also tried creating and flushing the ISession in each function of my repository. It made no sense because as soon as i was trying to load the TimeEntries property of an Employee, an exception was raised, saying the object could not be lazy-loaded as the ISession was closed...
I'm at lost and would appreciate some help. Any suggestion for my repository is also welcome, as i'm quite new to this.
Thanks you guys!
This code
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
is creating a new Employee object, presumable with an ID of 1 passed through the constructor. You should be loading the Employee from the database, and your Employee object should not allow the ID to be set since you are using an identity column. Also, a new Employee would not have any TimeEntries and the error message clearly points to an Employee instance as the problem.
I'm not a fan of transactions inside repositories and I'm really not a fan of generic repositories. Why is your EmployeeRepository a generic? Shouldn't it be
public class EmployeeRepository : IRepository<Employee>
I think your code should look something like:
var repository = new EmployeeRepository(session);
var emp = repository.GetById(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
repository.Save(emp);
Personally I prefer to work directly with the ISession:
using (var txn = _session.BeginTransaction())
{
var emp = _session.Get<Employee>(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
txn.Commit();
}
This StackOverflow Answer gives an excellent description of using merge.
But...
I believe that you are facing issues with setting up a correct session pattern for your application.
I you suggest to take a look at session-per-request pattern
where in you create a single NHibernate session object per request. the session is opened when the request is received and closed/flushed on generating a response.
Also make sure that instead of using SessionFactory.OpenSession() to get a session try using SessionFactory.GetCurrentSession() which puts the onus on NHibernate to return you the current correct session.
I hope this pushes you in the right direction.

Caching Results in a Static Variable

I'm using a Linq query to retrieve entities from an SQL server using the Entity Framework. When I update an entitiy, the EF is caching the result. I suspect this is because the ObjectContext is in a static variable (below). The only way to refresh the data using my code below is to call a method and set _db to null when there might be stale data displayed (Eg: in a GridView). Is there a way to just prevent it from caching, or to add some sort of end request handler to call this method on my data layer instead of needing to detect when there may be stale data displayed?
private static ServiceEntities _db;
protected static ServiceEntitiesDb
{
get
{
if (_db == null)
{
_db = new ServiceEntities();
_db.Contacts.MergeOption = MergeOption.OverwriteChanges; // failed
}
return _db;
}
}
public static IEnumerable<Contact> GetContactsByName(string name) {
var items = Db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}
The slightly verbose solution (which I wanted to avoid) is to wrap it in a using block. Ie:
public static IEnumerable<Contact> GetContactsByName(string name) {
var items = Db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}
Becomes
public static IEnumerable<Contact> GetContactsByName(string name) {
using (var db = new SomeContext()) {
var items = db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}
}

Resources