Insight.Database - using Insert with OpenWithTransaction - c#

First question ever :)
I'm a newbie at using the Insight.Database library but I think I'm using this in the right way. I'm running the (simplified) code below from a XUnit Test in another project in the same solution. The exception is thrown on the line with the connTran.Insert, and if I break on the logging function in the CATCH block and look at the message in the exception, it gives the error error CS7069: Reference to type 'DbConnectionWrapper' claims it is defined in 'Insight.Database', but it could not be found, but then the debugger will also break on the connTran.Rollback() line with A transaction has not been created for this connection.
What's weird is that I have used the same code in another Test in the same solution and test project but with a flat entity and it operated fine.
I'm using Visual Studio 2015 Enterprise. The debugger is also not behaving properly - the hovering over variables, etc doesn't work while "inside" the transaction. I've found a very similar github issue on Github here but no resolution that I can use.
This is the Insert code I am using - I have also tried connTran.Insert but I get the same result.
DbConnectionWrapper connTran = null;
try
{
using (connTran = _dbconnection.OpenWithTransaction())
{
var lookupHeader = connTran.QueryResults<Results>("usp_Lookup_InsertHeader", entity);
connTran.Commit();
}
}
catch(Exception ex)
{
logException(ex.Message);
connTran.Rollback();
throw;
}
The entity object looks like this:
public class LookupEntity
{
[RecordId]
public int LookupHeaderId { get; set; }
public string LookupHeaderName { get; set; }
public string LookupHeaderDescription { get; set; }
public string LookupHeaderCategory { get; set; }
public string LookupHeaderType { get; set; }
public string LookupBPMObjectName { get; set; }
public string LookupBPMMethodName { get; set; }
public string LookupBPMInputParams { get; set; }
public string LookupBPMExtraParams { get; set; }
public string LookupBPMOutputDataSetName { get; set; }
public string LookupBPMOutputNameNode { get; set; }
public string LookupBPMOutputValueNode { get; set; }
public string LookupBPMOutputActiveNode { get; set; }
public int Active { get; set; }
public int Cache { get; set; }
public int CsysLastUpdateBy { get; set; }
public DateTime? CsysLastUpdateDate { get; set; }
public int CsysInsertBy { get; set; }
public DateTime? CsysInsertDate { get; set; }
public string CsysTimeStamp { get; set; }
public string CsysTag { get; set; }
public int CsysOwnerId { get; set; }
public string CsysOwnerType { get; set; }
public int CsysRecordStatus { get; set; }
[ChildRecords]
public List<LookupDetail> LookupDetails { get; set; }
}

Well...a bit more messing around and poking through the Insight source code, I got past my original issue, and encountered a few more. I'm going to post my findings here in case the Insight author has a look.
The answer to my original issue was to restructure my TRY..CATCH to be inside the USING - this may have been obvious, maybe it wasn't but now I know :) So my code turned into this:
using (var connTran = _dbconnection.OpenWithTransaction())
{
try
{
connTran.Insert("usp_Lookup_InsertHeader", entity, new { lookupHeader = entity });
connTran.Commit();
}
catch(Exception ex)
{
logException(ex.Message);
connTran.Rollback();
throw;
}
}
Note I could also get rid of declaring the connTran variable outside of the using.
Since I'm using SQL 2008, I was keen to use the Table Value Parameters with the Insert stored procedure. This gave me my next head-scratcher - which may be related to out-of-date documentation.
The doco states that code like this:
connTran.Insert("usp_Lookup_InsertHeader", entity);
would insert the record and then map the new identity value back into the entity, assuming the returned id field and entity property names match up (which they do). The Insert stored procedure has a signature like this:
CREATE PROCEDURE [dbo].[usp_Lookup_InsertHeader]
#lookupHeader [lookupHeaderType] READONLY
Insight kept complaining that the "lookupHeader" parameter was not defined, so I eventually stumbled across something elsewhere in the doco that turned my code into this:
connTran.Insert("usp_Lookup_InsertHeader", entity, new { lookupHeader = entity });
Now Insight is happy :)
The third issue then became datetime values.
The CsysLastUpdateDate property in the entity was defined as DateTime?. In the SQL Type for the TVP, the CsysLastUpdateDate field was defined as DateTime. In my Test, I set the CsysLastUpdateDate to DateTime.Now
Watching SQL Profiler, I found that the SQL text Insight was posting included the milliseconds in what is now a string representation of the datetime value. There is a sample of this text below with the string datetime - '2016-06-16 18:03:32.5510000'.
declare #p1 dbo.lookupHeaderType
insert into #p1 values(0,N'timbo.test',N'',N'',N'',N'',N'',N'',N'',N'',N'',N'',N'',1,1,2,'2016-06-16 18:03:32.5510000',2,'2016-06-16 18:03:32.5510000',N'',N'',1,N'cSysSite',1)
When SQL tried to execute that text to create the TVP, it errored with a datetime conversion issue. If I manually edited the milliseconds out of the datetime string and executed the text, the TVP created properly.
With some more playing around I discovered that declaring the CsysLastUpdateDate field in the Type as a DateTime2 field, the SQL that Insight was sending executed aok and the Insert worked happily.
I'm not sure if I've found bugs or these are just newbie learnings, but I hope this helps the next person :)

Related

Returning different versions of set of data

So I have a class with a static method that uses EF to retrieve certain set of mailing lists and maps to the class.
public static List<MailingList> GetMailingListsForUser(IUsersAccess user, IProspectorDataSource db )
{
return db.MailingLists.Where(x => x.UserID == user.UserID).ToList()
.Select(y => new MailingList(y, db) ).ToList();
}
Now though I have a proc that will return the MailingList plus some extra stuff. I don't want to add these extra columns (which will be used in other sections and areas of functionality) to this class. What is the best way to address this?
I am thinking a Factory Pattern that will generate a different class that implements different contracts (interfaces) based on whats needed. Going to try implement it and will post code/working when completed.
Was wondering what other people have done in instances like this and if there are any better ways to address this.
Edit: (some extra information to help people understand what I mean).
public class MailingList
{
public int MailingListID { get; set; }
public string Name { get; set; }
public string Comments { get; set; }
public List<string> Tags { get; set; }
public int UserID { get; set; }
public System.DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public System.DateTime LastModified { get; set; }
public string ModifiedBy { get; set; }
public List<MailingListAddress> MailingListAddresses { get; set; }
That is the definition of an object that we return. Now there is a new instance where I am going to return some extra columns from a proc and map to MailingList. So I could just add the properties to here but the issue is MailingListAddresses will be null as they will not be returned by the stored proc. So is there a way to map to specific properties and not have to return null for MailingListAddresses to the front end every time.
This was fixed by a senior developer who ended up going with the factory pattern. I will add the code when I get back to work :)

Recovery of class containing a list returns a null list

I have a couple of classes:
public class MyGoalsModel
{
[Key]
public string Name { get; set; }
/*Some local bools*/
public List<MyGoalString> myGoals { get; set; }
}
public class MyGoalString
{
public int MyGoalStringID { get; set; }
public string GoalString { get; set; }
public bool Selected { get; set; }
}
I can populate them correctly, and the code (EF?) generates the necessary hidden foreign keys to link them (all ok in SQL) and recover the information for MyGoalsModel, but the List is always null.
I use the following to get the entry I want:
MyGoalsModel goals = db.MyGoals.Find(Name);
but when I investigate the code goals.MyGoals is always null.
Am I missing something, is there a better way to recover the information with the lists present?
Add the keyword virtual so EF can create a proxy for your List and lazy load the data when needed.
Edit: Or as stated in the accepted answer in this question.

Where did the overload of DbQuery.Include() go that takes a lambda?

I just declared some code-first models for a new project that uses EntityFramework.
public class BlogEntry
{
public long Id { get; set; }
public long AuthorId { get; set; }
public DateTime PublishedStamp { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public virtual User Author { get; set; }
}
public class User
{
public long Id { get; set; }
public string Email { get; set; }
// ...
}
class BlogDb : DbContext
{
public DbSet<BlogEntry> Entries { get; set; }
public DbSet<User> Users { get; set; }
}
Now suppose I want to retrieve the 10 most recent blog entries:
var entries = new BlogDb().Entries.OrderByDescending(...).Take(10).ToList();
The problem now is that accessing entry.Author will cause another database query. You wouldn’t want a separate such query for every blog entry. Now, it is my understanding that the purpose of Include is exactly this case, so I can say:
var entries = new BlogDb().Entries.Include(e => e.Author).(...).ToList();
However, that method doesn’t seem to exist. There is only an Include(string), like this:
var entries = new BlogDb().Entries.Include("Author").(...).ToList();
but this is annoying because it’s not compile-time checked and will be missed by the rename refactoring. Surely the version with the lambda is the “correct” approach.
Where did that method go? Is it no longer included in EntityFramework?
(I know that I can write an extension method for myself to achieve this, so you don’t have to. I’d just like to know whether I’m missing something.)
using System.Data.Entity;
It's in EF v4.1 and above, but you need a reference as it is an extension method.
Edit (thanks to #EastonJamesHarvey)
If using EF Core the import should be:
using Microsoft.EntityFrameworkCore;

Strange entity updating in Entity Framework Code-First

I'm solving the problem with updating entity before saving to database and got strange behavior.
I'm using Entity Framework 4.1 Code-First in ASP.NET MVC 3 web application. Here is model:
public class Order
{
public int OrderId { get; set; }
public int CarId { get; set; }
public DateTime BeginRentDate { get; set; }
public DateTime EndRentDate { get; set; }
public decimal RentPrice { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string NumberPlate { get; set; }
public decimal RentPrice { get; set; }
}
Each Car has a RentPrice. This price should be copied to Order's RentPrice when creating one. The car is selecting by user so initially Order.RentPrice is 0.
Here I want to copy price value:
[HttpPost]
public ActionResult Create(Order order)
{
order.RentPrice = _context.Cars.Find(order.CarId).RentPrice;
if (ModelState.IsValid)
{
_context.Orders.Add(order);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
It's not working because of an error on the SaveChanges that entity has validation errors. OK. I found that need first to call UpdateModel(order); and then change values.
So what I have. Working code:
_context.Orders.Add(order);
UpdateModel(order);
order.RentPrice = 777;
_context.SaveChanges();
Not working code:
_context.Orders.Add(order);
UpdateModel(order);
order.RentPrice = _context.Cars.Find(order.CarId).RentPrice;
_context.SaveChanges();
Working code (!):
_context.Orders.Add(order);
UpdateModel(order);
var t = (double)_context.Cars.Find(order.CarId).RentPrice;
order.RentPrice = (decimal)t;
_context.SaveChanges();
Can someone explain, please, what is going on here? Especially magic on the 3nd and 4th lines in the last block of code.
Update
I'm getting DbEntityValidationException: "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details."
From the inner exception: "OriginalValues cannot be used for entities in the Added state."
When you get
"Validation failed for one or more entities. See
'EntityValidationErrors' property for more details." From the inner
exception: "OriginalValues cannot be used for entities in the Added
state."
It means there was errors such as NOT NULL collumns that were blank or other constraints , check the entity validation errors by debugging or like
try{
...
catch ( DbEntityValidationException ex )
{
foreach ( var validationErrors in ex.EntityValidationErrors )
{
foreach ( var validationError in validationErrors.ValidationErrors )
{
System.Diagnostics.Trace.TraceInformation( "Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage );
}
}
}
OriginalValues cannot be used for entities in the Added state.
Can be corrected by ensuring that non identity primary key fields are specified as not generated in the model.
modelBuilder
.Entity<T>()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// this can also be achieved using attributes on the entity.
The error is actually self explanatory when you have context, but baffling otherwise. The database generates two statements for the insert, the second of which is:
SELECT [PrimaryKeyId]
FROM [dbo].[Entity]
WHERE ##ROWCOUNT > 0 AND [PrimaryKeyId] = scope_identity()
/* SP:StmtCompleted... */
This statement will not return any rows for non identity columns. Hence the OriginalValues will remain unaltered in the added state. This also explains why this exception is wrapped in an OptimisticConcurrencyException as the number of rows affected is used to detect existing altered data.
Have you declared the primary key anywhere? You should do that either using FluentAPI or attribute like this:
public class Order
{
[Key]
public int OrderId { get; set; }
public int CarId { get; set; }
public DateTime BeginRentDate { get; set; }
public DateTime EndRentDate { get; set; }
public decimal RentPrice { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
[Key]
public int CarId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string NumberPlate { get; set; }
public decimal RentPrice { get; set; }
}
Or in your Context, you can use Fluent API to declare the key, like this
builder.Entity<Car>().HasKey(x=>x.CarId);
builder.Entity<Order>().HasKey(x=>x.OrderId);
I have an idea, but it's more of a guess... Maybe that by the time you get in that Create function, the context already knows of that Order? If it does, trying to re-add it again could maybe give you that error.
In your last piece of code (the one that surprisingly work), have you tried to use an intermediary var without casting to double?
I'm also intrigued of that UpdateModel... I use EF but not MVC so maybe it has nothing to do about it, but if it's a custom function, what does it do?
I am sure the problem would have been rectified ages ago, just wanted to contribute my 2 cents.
I also ran into an issue with the same error message, I was using Oracle and EF 5.0. Finally I got a solution after wasting more than 12 hours.
For me it was lack of the permissions for the user on the table where I was trying to insert values and the error message absolutely had no hints of the actual permission issue, which was really weird.
Hope someone finds this useful and doesn't waste hours like me in troubleshooting.
Cheers,
Avi

How to save a MySQL timestamp as a String instead of a DateTime using AutoMapper?

I'm using AutoMapper to save my MySQL results in a List, this is complete with information on when the row was last updated. The 'timestamp' will then be used to query the database for updates. But, I'm afraid that using C# DateTime type, will modify the timezone, depending on the location of the user. As I experienced this problem earlier in the development cycle.
So, basically my question is how do I make sure that the 'timestamp' saved using AutoMapper isn't modified and can be used again to query the database?
Edit: This is the code used to convert the results.
public class Entry
{
public UInt32 id { get; set; }
public string ... { get; set; }
public UInt16 ... { get; set; }
public string ... { get; set; }
public string lastupdated { get; set; } // Using DateTime works, also tried value.ToString()
public string ... { get; set; }
public UInt16 ... { get; set; }
}
List<Entry> users = AutoMapper.Mapper.Map<MySqlDataReader,List<Entry>>(dbReader);
You can implement this using a custom formatter.
For more detail on how to implement such a formatter, see this article at CodeProject: http://www.codeproject.com/KB/codegen/automapperformatters.aspx.

Categories