Is it possible to generate SQL scripts using OrmLite without executing it against a database? I would like to load a list of DTOs from a live SqlServer database and output a script to DELETE and INSERT each record.
The provided mini profiler supports logging, but looks like it needs to wrap a real database connection.
This is trivial now that OrmLite extension methods are now mockable by providing your own OrmLiteResultsFilter.
E.g. this ResultsFilter below records every sql statement executed and inherts the behavior from OrmLiteResultsFilter to return empty results:
public class CaptureSqlFilter : OrmLiteResultsFilter
{
public CaptureSqlFilter()
{
SqlCommandFilter = CaptureSqlCommand;
SqlCommandHistory = new List<SqlCommandDetails>();
}
private void CaptureSqlCommand(IDbCommand command)
{
SqlCommandHistory.Add(new SqlCommandDetails(command));
}
public List<SqlCommandDetails> SqlCommandHistory { get; set; }
public List<string> SqlStatements
{
get { return SqlCommandHistory.Map(x => x.Sql); }
}
}
You can wrap this in an using scope to capture each SQL statement without executing them, e.g:
using (var captured = new CaptureSqlFilter())
using (var db = OpenDbConnection())
{
db.CreateTable<Person>();
db.Select<Person>(x => x.Age > 40);
db.Single<Person>(x => x.Age == 42);
db.Count<Person>(x => x.Age < 50);
db.Insert(new Person { Id = 7, FirstName = "Amy", LastName = "Winehouse" });
db.Update(new Person { Id = 1, FirstName = "Jimi", LastName = "Hendrix" });
db.Delete<Person>(new { FirstName = "Jimi", Age = 27 });
db.SqlColumn<string>("SELECT LastName FROM Person WHERE Age < #age",
new { age = 50 });
db.SqlList<Person>("exec sp_name #firstName, #age",
new { firstName = "aName", age = 1 });
db.ExecuteNonQuery("UPDATE Person SET LastName={0} WHERE Id={1}"
.SqlFmt("WaterHouse", 7));
var sql = string.Join(";\n\n", captured.SqlStatements.ToArray());
sql.Print();
}
Which prints out:
CREATE TABLE "Person"
(
"Id" INTEGER PRIMARY KEY,
"FirstName" VARCHAR(8000) NULL,
"LastName" VARCHAR(8000) NULL,
"Age" INTEGER NOT NULL
);
;
SELECT "Id", "FirstName", "LastName", "Age"
FROM "Person"
WHERE ("Age" > 40);
SELECT "Id", "FirstName", "LastName", "Age"
FROM "Person"
WHERE ("Age" = 42)
LIMIT 1;
SELECT COUNT(*) FROM "Person" WHERE ("Age" < 50);
INSERT INTO "Person" ("Id","FirstName","LastName","Age") VALUES (#Id,#FirstName,#LastName,#Age);
UPDATE "Person" SET "FirstName"=#FirstName, "LastName"=#LastName, "Age"=#Age WHERE "Id"=#Id;
DELETE FROM "Person" WHERE "FirstName"=#FirstName AND "Age"=#Age;
SELECT LastName FROM Person WHERE Age < #age;
exec sp_name #firstName, #age;
UPDATE Person SET LastName='WaterHouse' WHERE Id=7
More examples available in CaptureSqlFilterTests.cs
As CaptureSqlFilter is useful I've just added it to OrmLite in this commit which will be in the next v4.0.20 that's now available on MyGet.
Using the DialectProvider directly seems to work well enough for what I need. ToInsertRowStatement takes a IDbCommand paramater, but does not use it so null works.
OrmLiteConfig.DialectProvider = SqlServerOrmLiteDialectProvider.Instance;
var dto = new PersonDTO { Id = Guid.NewGuid(), Name = "Carl" };
var deleteText = SqlServerOrmLiteDialectProvider.Instance.ToDeleteRowStatement(dto);
var insertText = SqlServerOrmLiteDialectProvider.Instance.ToInsertRowStatement((IDbCommand)null, dto);
Is there a better alternative?
I use this to capture the statement and keep running the sentense.
public class CustomOrmLiteExecFilter : OrmLiteExecFilter
{
public override T Exec<T>(IDbConnection dbConn, Func<IDbCommand, T> filter)
{
var holdProvider = OrmLiteConfig.DialectProvider;
var dbCmd = CreateCommand(dbConn);
try
{
var ret = filter(dbCmd);
var pureSQL = holdProvider.MergeParamsIntoSql(dbCmd.CommandText, dbCmd.Parameters.OfType<IDbDataParameter>());
//log or save the SQL Statement
return ret;
}
finally
{
if (OrmLiteConfig.DialectProvider != holdProvider)
OrmLiteConfig.DialectProvider = holdProvider;
}
}
}
and the usage:
OrmLiteConfig.ExecFilter = new CustomOrmLiteExecFilter();
hope this can help you!
Related
I have a dynamo db table CustomerOrders with following fields
Primary partition key: CustomerId (Number)
Primary sort key: DepartmentId (Number)
Order (Serialized Json String)
I would like to do a query on multiple customers in one request without using Sort Key (DepartmentId). So I created a Global Secondary Index on CustomerId and would like to use that to query just using the CustomerId. I see documentation only related to BatchGetItemAsync for running batch queries. I don't see a way to set the IndexName on a BatchGetItemRequest. How can that be done?
Below is my code segment so far:
public async Task<List<CustomerOrder>> GetOrdersAsync(List<int> customerIds)
{
var orders = new List<CustomerOrder>();
var tableKeys = new List<Dictionary<string, AttributeValue>>();
foreach (var x in customerIds)
{
tableKeys.Add(new Dictionary<string, AttributeValue> { { "CustomerId", new AttributeValue { N = x.ToString() } } });
}
var dynamoTable = $"CustomerOrders";
var keysAndAttributes = new KeysAndAttributes
{
AttributesToGet = new List<string> { "CustomerId", "DepartmentId", "Order" },
Keys = tableKeys
};
var request = new BatchGetItemRequest
{
ReturnConsumedCapacity = ReturnConsumedCapacity.INDEXES, // Not sure what this does
RequestItems = new Dictionary<string, KeysAndAttributes> { { dynamoTable, keysAndAttributes } }
};
BatchGetItemResponse result;
do
{
result = await dynamoDbClient.BatchGetItemAsync(request); // Exception gets thrown from here
var responses = result.Responses;
foreach (var tableName in responses.Keys)
{
var tableItems = responses[tableName];
foreach (var item in tableItems)
{
orders.Add(new CustomerOrder
{
CustomerId = int.Parse(item["CustomerId"].N),
DepartmentId = int.Parse(item["DepartmentId"].N),
Order = JsonConvert.DeserializeObject<Order>(item["Order"].S)
});
}
}
// Set RequestItems to the result's UnprocessedKeys and reissue request
request.RequestItems = result.UnprocessedKeys;
} while (result.UnprocessedKeys.Count > 0);
return orders;
}
I am getting The provided key element does not match the schema error with the above code. Please help!
You can't "set the IndexName on a BatchGetItemRequest"
In fact, you can't GetItem() on a GSI/LSI either. GetItem() only works on the table.
And GetItem() always requires the full primary key.
With a partial key, you'd need to perform multiple Query(), one for each hash key.
The GSI isn't doing anything for you. Department as a sort key really isn't doing anything for you either since I assume customerId is unique.
A better structure might have been to have the table defined with only hash key for the primary key;
I am currently implementing a Database collection/fixture for my unit tests, as documented on this question here:
xUnit.net - run code once before and after ALL tests
However, instead of using an InMemory Database, I'm using SQLite as InMemory currently has a bug in .Net Core 2.1 which doesn't do a sequence check when using a byte array type
Which leads me to my current predicament, namely that the byte array when you set up a database fixture doesn't get pulled through to the unit test when the context is pulled from the Database Fixture and into the unit test, which is causing concurrency errors when I try to run the tests.
As an example:
Fist set the DatabaseFixture class like so:
public class DatabaseFixture : IDisposable
{
public DatabaseFixture()
{
var connectionStringbuilder = new SqliteConnectionStringBuilder{DataSource = ":memory:", Cache = SqliteCacheMode.Shared};
var connection = new SqliteConnection(connectionStringbuilder.ToString());
options = new DbContextOptionsBuilder<CRMContext>()
.UseSqlite(connection)
.EnableSensitiveDataLogging()
.Options;
using (var context = new CRMContext(options))
{
context.Database.OpenConnection();
context.Database.EnsureCreated();
context.Persons.AddRange(persons);
context.SaveChanges();
}
}
public DbContextOptions<CRMContext> options { get; set; }
public void Dispose()
{
using (var context = new CRMContext(options))
{
context.Database.CloseConnection();
context.Dispose();
}
}
private IQueryable<Person> persons = new List<Person>()
{
new Person
{
Id = 1,
Forename = "Test",
Surname = "User",
RowVersion = new byte[0]
},
new Person
{
Id = 2,
Forename = "Another",
Surname = "Test",
RowVersion = new byte[0]
}
}.AsQueryable();
}
Setup your empty DatabaseCollection class as per the first link:
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}
Then set up your unit test to use this Database Fixture:
[Collection("Database collection")]
public class PersonTests : BaseTests
{
private readonly DatabaseFixture _fixture;
public PersonTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void SaveAndReturnEntityAsync_SaveNewPerson_ReturnsTrue()
{
{
using (var context = new Context(_fixture.options))
{
var existingperson = new Person
{
Id = 2,
Forename = "Edit",
Surname = "Test",
RowVersion = new byte[0]
};
var mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new InitializeAutomapperProfile()); });
var AlertAcknowledgeService = GenerateService(context);
//Act
//var result = _Person.SaveAndReturnEntityAsync(mappedPersonAlertAcknowledge);
//Assert
Assert.Equal("RanToCompletion", result.Status.ToString());
Assert.True(result.IsCompletedSuccessfully);
Assert.Equal("The data is saved successfully", result.Result.SuccessMessage);
}
}
Now when I debug this, it hits the fixture correctly, and you can when you expand the Results view, the RowVersion variable is assigned correctly:
However, when the data is passed into the unit test, the row version gets set to null:
Any help on this would be greatly appreciated!
I have the following code:
dbcon = DependencyService.Get<ISQLite>().GetConnection();
// create the tables
dbcon.CreateTable<Category>();
dbcon.CreateTable<Settings>();
var settings = dbcon.Table<Settings>().ToList();
if (settings.Count <= 0)
{
var noa = new Settings { Setting = "NumberOfAnswers", Value = 5 };
var cfs = new Settings { Setting = "CardFrontSide", Value = 0 };
dbcon.Insert(noa);
dbcon.Insert(cfs);
}
var categories = dbcon.Table<Category>().ToList();
if (categories.Count <= 0)
{
InsertCategory();
}
From what I can see the application is using SQLite-net
What I would like to know is if there is a way I can check to see if a table exists rather than do this which is to try and create it anyway and then try to check if it has rows in it.
This query will return the list of tables in the database
SELECT * FROM sqlite_master WHERE type = 'table';
You can filter it down to a single row for an "exists" check.
SELECT * FROM sqlite_master WHERE type = 'table' AND tbl_name = 'xyz';
You can do something like this:
public static bool TableExists<T> (SQLiteConnection connection)
{
const string cmdText = "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
var cmd = connection.CreateCommand (cmdText, typeof(T).Name);
return cmd.ExecuteScalar<string> () != null;
}
Source
You can use the below codes
public bool IsTableExists(string tableName)
{
try
{
var tableInfo = database.GetConnection().GetTableInfo(tableName);
if(tableInfo.Count > 0)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
public SQLiteAsyncConnection database;
public ClientDatabase(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
}
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
I am writting ToList or Toarray is it not working the error comes :
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
From your code I assume your adding a custom property cell for display/storage purposes on the client-side. I would avoid this as your essentially coupling your API call to one particular client. I would suggest you simply return the data required & deal with it at the client-side specifically e.g.
Server
...
select new
{
Id = n.AuthorId,
Name = n.Name,
Location = n.Location
}).ToList();
...
Client
var response = ...
foreach (var author in response)
{
var cell = new string[] { author.Id.ToString(), author.Name, author.Location };
// do something with cell
}
You should try SqlFunctions.StringConvert to convert this, There is no overload for int so you should cast your number to a double or a decimal.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { SqlFunctions.StringConvert((double)n.AuthorId), n.Name, n.Location }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
You are not using LinqToSql Classes, if you were using that your code should work, but as you mention that you are using LinqToEntity then You should use SqlFunctions.StringConvert to convert to string.
I'd like to know how to run this query in Linq way.
UPDATE orders SET shipDate = '6/15/2012' WHERE orderId IN ('123123','4986948','23947439')
My Codes,
[HttpGet]
public void test()
{
EFOrdersRepository ordersRepository = new EFOrdersRepository();
var query = ordersRepository.Orders;
// How to run this query in LINQ
// Query : UPDATE orders SET shipDate = '6/15/2012' WHERE orderId IN ('123123','4986948','23947439')
}
EFOrdersRepository.cs
public class EFOrdersRepository
{
private EFMysqlContext context = new EFMysqlContext();
public IQueryable<Order> Orders
{
get { return context.orders; }
}
}
EFMysqlContext.cs
class EFMysqlContext : DbContext
{
public DbSet<Order> orders { get; set; }
}
Actually it's pretty easy check the following code
EFOrdersRepository db = new EFOrdersRepository();
int[] ids= new string[] { "123123", "4986948", "23947439"};
//this linq give's the orders with the numbers
List<Order> orders = db.Order().ToList()
.Where( x => ids.Contains(x.orderId.Contains));
foreach(var order in orders)
{
order.ShipDate = '06/15/2012';
db.Entry(usuario).State = EntityState.Modified;
}
db.SaveChanges();
Something like this should work (warning Pseudo code ahead!!)
EDIT I like using the Jorge's method of retrieving the orders better (using contains), but leaving this here as another alternative. The statements below the code sample still hold true however.
[HttpGet]
public void test()
{
EFOrdersRepository ordersRepository = new EFOrdersRepository();
var query = ordersRepository.Orders.Where(x=>x.orderId == '123123' ||
x.orderId == '4986948' || x.orderId = '23947439').ToList();
foreach(var order in query){
var localOrder = order;
order.ShipDate = '06/15/2012';
}
ordersRepository.SaveChanges();
}
Basically, LINQ does not do 'bulk updates' well. You either have to fetch and loop through your orders or write a stored procedure that can take an array of ids and bulk update them that way. If you are only doing a few at a time, the above will work ok. If you have tons of orders that need to be updated, the ORM probably will not be the best choice. I look forward to see if anyone else has a better approach.
Disclaimer: the var localOrder = order line is to ensure that there are no modified closure issues. Also, ReSharper and other tools may have a less verbose way of writing the above.
Note: You need to call SaveChanges from your DBContext at the end
Short answer:
var f = new[] { 123123, 4986948, 23947439 };
var matchingOrders = orders.Where(x => f.Contains(x.ID)).ToList();
matchingOrders.ForEach(x => x.ShipDate = newDate);
Complete test:
// new date value
var newDate = new DateTime(2012, 6, 15);
// id's
var f = new[] { 123123, 4986948, 23947439 };
// simpulating the orders from the db
var orders = Builder<Order2>.CreateListOfSize(10).Build().ToList();
orders.Add(new Order2 { ID = 123123 });
orders.Add(new Order2 { ID = 4986948 });
orders.Add(new Order2 { ID = 23947439 });
// selecting only the matching orders
var matchingOrders = orders.Where(x => f.Contains(x.ID)).ToList();
matchingOrders.ForEach(x => Console.WriteLine("ID: " + x.ID + " Date: " + x.ShipDate.ToShortDateString()));
// setting the new value to all the results
matchingOrders.ForEach(x => x.ShipDate = newDate);
matchingOrders.ForEach(x => Console.WriteLine("ID: " + x.ID + " Date: " + x.ShipDate.ToShortDateString()));
Output:
ID: 123123 Date: 1/1/0001
ID: 4986948 Date: 1/1/0001
ID: 23947439 Date: 1/1/0001
ID: 123123 Date: 6/15/2012
ID: 4986948 Date: 6/15/2012
ID: 23947439 Date: 6/15/2012
In ORMs, You have to fetch the record first make the change to the record then save it back. To do that, I will add an UpdateOrder method to my Repositary like this
public bool UpdateOrder(Order order)
{
int result=false;
int n=0;
context.Orders.Attach(order);
context.Entry(order).State=EntityState.Modified;
try
{
n=context.SaveChanges();
result=true;
}
catch (DbUpdateConcurrencyException ex)
{
ex.Entries.Single().Reload();
n= context.SaveChanges();
result= true;
}
catch (Exception ex2)
{
//log error or propogate to parent
}
return result;
}
And i will call it from my Action method like this
int orderId=123232;
var orders=ordersRepository.Orders.Where(x=> x.orderId.Contains(orderId)).ToList();
if(orders!=null)
{
foreach(var order in orders)
{
order.ShipDate=DateTime.Parse('12/12/2012);
var result= ordersRepository.UpdateOrder();
}
}
In this Approach, if you have to update many number of records, you are executing thatn many number of update statement to the database. In this purticular case, i would like to execute the Raw SQL statement with only one query using the Database.SqlQuery method
string yourQry="UPDATE orders SET shipDate = '6/15/2012'
WHERE orderId IN ('123123','4986948','23947439')";
var reslt=context.Database.SqlQuery<int>(yourQry);