For the life of me I can't save my poco's after an update, the insert works though.
Below is the code in question:
public class Campaign : IHasId<int>, IAudit
{
public Campaign()
{
IsRetread = true;
IsDuplicate = true;
}
[AutoIncrement]
public int Id { get; set; } //CampaignID
public long CreatedDate { get; set; }
public long ModifiedDate { get; set; }
public string ModifiedBy { get; set; }
[Required]
public string Name { get; set; } //CampaignName
[Required]
public int CampaignTypeId { get; set; } //CampaignType
[Required]
public int CampaignDeliveryTypeId { get; set; } //LeadDeliveryType
public string CampaignPhone { get; set; }
[Required]
public int LeadProviderId { get; set; } //LeadProviderID
public int PhoneTriggerId { get; set; }
[Required]
public int CampaignResponseId { get; set; } //LeadResponse
[Required]
public int CampaignCostTypeId { get; set; } //CostTypeID
public decimal CostAmount { get; set; } //CostAmount
public decimal FixedCost { get; set; } //FixedCost
public string NoteMisc { get; set; } //NoteTxt
public string NoteAgent { get; set; } //MessagetoAgents
public bool IsDefaultCampaign { get; set; } //DefaultCampaign
public bool IsExceptionCampaign { get; set; } //ExceptionCampaign
public bool IsFirmOffer { get; set; } //Firm Offer
[Reference]
public CampaignCreative CampaignCreative { get; set; }
[Reference]
public List<CampaignRule> CampaignRules { get; set; }
/* These really should be a collection of rules */
public bool IsDuplicate { get; set; } //IsDuplicate
public bool IsRetread { get; set; } //IsRetread
public bool IsFactorTrustLeads { get; set; } //IsFactorTrustLeads
public bool IsFactorTrustApp { get; set; } //IsFactorTrustApp
}
Then the save, which works:
public long SaveCampaign(Campaign campaign)
{
using (var db = _connectionFactory.OpenDbConnection())
{
var rowId = db.Insert(campaign, true);
return rowId;
}
}
and the update, which never saves the changed poco:
public long UpdateCampaign(Campaign campaign)
{
using (var db = _connectionFactory.OpenDbConnection())
{
db.Save(campaign, true);
return campaign.Id;
}
}
There aren't any errors, and the only sql I see in the immediate window are select statement, no updates (I've never seen ANY other statements beside SELECTs)
Is there another way to see why this update fails?
Thank you, Stephen
View Last SQL Executed
The easiest approach is to just print out the last SQL Statement that was executed, i.e:
db.GetLastSql().Print();
This works well for most OrmLite API's which only execute a single SQL statement. But it wont show all SQL executed with OrmLite's higher-level API's like db.Save() which can execute multiple statements.
Use Captured OrmLite SQL Filter
Another approach is to capture the generated SQL using a Capture Exec Filter which instead of executing the SQL Statements will
using (var captured = new CaptureSqlFilter())
using (var db = _connectionFactory.OpenDbConnection())
{
db.CreateTable<Campaign>();
var rowId = db.Insert(campaign, true);
var sql = string.Join(";\n", captured.SqlStatements.ToArray());
sql.Print();
}
But as this only captures and doesn't execute the SQL it wont show the full story of API's that rely on reading the database (which will just return mocked/empty results).
Profile the DB Connection
A more comprehensive approach which is used by many SQL Profilers is to use a connection profiler which is just a wrapper around a DbConnection that in addition to executing the SQL will also capture and profile it.
Here's an example of profiling OrmLite by using ServiceStack's built-in Mini Profiler:
Container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(
connectionString, SqlServerDialect.Provider) {
ConnectionFilter = x => new ProfiledDbConnection(x, Profiler.Current)
});
Which gets OrmLite to execute its SQL using a ProfiledDbConnection instead which can be later viewed in the Mini Profiler.
Related
I'm quite new to .net and entity framework (this is my first project) and I'm getting the following error when trying to update the database:
*Introducing FOREIGN KEY constraint 'FK_Rating_User_UserId' on table 'Rating' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.*
I tried doing what it says (at least I think so) by adding the following to my dbContext class:
protected override void OnModelCreating(ModelBuilder modelbuilder)
{
modelbuilder.Entity<Rating>().HasOne(u => u.User).WithMany().OnDelete(DeleteBehavior.Restrict);
modelbuilder.Entity<Rating>().HasOne(g => g.Game).WithMany().OnDelete(DeleteBehavior.Restrict);
}
Not sure have I formulated that method correctly but it did not help (I tried with different DeleteBehavior like SetNull and NoAction)
The thing that really got me confused is that the issue appears even after removing all fields related to other tables from Rating class or even all references between all classes.
My Rating class:
public class Rating
{
public long RatingId { get; set; }
//[Rating]
public virtual Game Game { get; set; } // issue appears even after removing this and User line
//[Rating]
public int Score { get; set; }
public string CommentTitle { get; set; }
public string CommentDescription { get; set; }
//[Rating]
public virtual User User { get; set; }// issue appears even after removing this and Game line
}
User class:
public class User
{
public long UserId { get; set; }
//[Required]
public bool IsModerator { get; set; }
//[Required]
public string Username { get; set; }
//[Required]
public string Email { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
//[Required]
public string Password { get; set; }
//[Required]
public string Salt { get; set; }
public string Description { get; set; }
}
Game class:
public class Game
{
public long GameId { get; set; }
//[Required]
public virtual User User { get; set; }
//[Required]
public string Title { get; set; }
public string Description { get; set; }
//[Required]
public string PricingType { get; set; }
public float MinDonation { get; set; }
public float MaxDonation { get; set; }
//[Required]
public string FileLocation { get; set; }
public float AverageRaiting { get; set; }
public int DownloadCount { get; set; }
}
GameImage class (probably unrelated to the issue just wanted to give a full context)
public class GameImage
{
public long GameImageId { get; set; }
//[Required]
public virtual Game Game { get; set; }
//[Required]
public string Location { get; set; }
//[Required]
public bool IsThumbnail { get; set; }
}
dbContext class:
public class dbContext : DbContext
{
public dbContext(DbContextOptions<dbContext> options) : base(options)
{
}
public DbSet<User> User { get; set; }
public DbSet<Rating> Rating { get; set; }
public DbSet<GameImage> GameImage { get; set; }
public DbSet<Game> Game { get; set; }
}
The issue only appeared after I tried to update the database. The first few migrations and updates were ok, however, then I tried adding [Required] annotation (you can see them commented in the above code) as I noticed that most of the fields were created as nullable in my database - after that the issue starting to occur even after removing the annotations.
In case that matters, I'm using Visual Studio 2019 and SQL Server Express
Does anyone have any idea what may be the cause of this?
Edit:
Image of of my database schema diagram from SSMS
As you can see in the database schema it's visible that there are indeed cycles in the database, however, I cannot get rid of them as Entity Framework's command "Update-Database" does not update the DB and just throws the error mentioned above.
Based on my test, you can try the following steps to solve the problem.
First, please change your dbcontext class into the following code.
public class dbContext : DbContext
{
public dbContext() : base("name=MyContext") { }
public DbSet<User> User { get; set; }
public DbSet<Rating> Rating { get; set; }
public DbSet<GameImage> GameImage { get; set; }
public DbSet<Game> Game { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Second, please delete all the tables the database.
Third, please try the following command in your package console.
PM> Update-Database -Force
Finally, you can see the new tables in the databse.
Im trying to create a persistent sqllite db (creation tables once you install the app, deletion of db when you uninstall app)
I have a issue that I cant save my sub objects for example
public class ObjectInstanceResponseModel : GenericResponseModel
{
public ObservableCollection<ObjectInstanceData> ObjectInstances { get; set; }
}
public class ObjectInstanceData : GenericResponseModel
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[JsonProperty("idObjectinstance")]
public int IdObjectInstance { get; set; }
[JsonProperty("objectclass_idObjectclass")]
public int ObjectClassIdObjectClass { get; set; }
[JsonProperty("objectclassname")]
public string ObjectClassName { get; set; }
[JsonProperty("visibilitylevel")]
public int VisibilityLevel { get; set; }
[JsonProperty("showname")]
public bool ShowName { get; set; }
[JsonProperty("showicon")]
public bool ShowIcon { get; set; }
[JsonProperty("creationtime")]
public DateTimeOffset CreationTime { get; set; }
[JsonProperty("users_idUsers")]
public int UsersIdUsers { get; set; }
[JsonProperty("isfavorite")]
public bool? IsFavorite { get; set; }
[OneToMany("ObjectInstanceDataStrings")]
[JsonProperty("strings")]
public List<String> Strings { get; set; }
}
public class String
{
[PrimaryKey, AutoIncrement]
[JsonProperty("idObjectparameterstring")]
public int? IdObjectParameterString { get; set; }
[ForeignKey(typeof(ObjectInstanceData))]
public int ObjectInstanceDataStrings { get; set; }
[JsonProperty("stringvalue")]
public string StringValue { get; set; }
[JsonProperty("objectparameterset_idObjectparameterset")]
public int? ObjectParameterSetIdObjectParameterSet { get; set; }
[JsonProperty("showinballon")]
public bool? ShowInBallon { get; set; }
[JsonProperty("idClassparameter")]
public int IdClassParameter { get; set; }
[JsonProperty("classparametername")]
public string ClassParameterName { get; set; }
}
So, my class String is always empty, although there are some rows in the table that I created Strings..
Am I need a lazy loading for this?
I implemented sqllite through depedency service in my app.cs like this:
public partial class App : Application
{
public static SQLiteConnection DatabaseConnection { get; set; }
public App()
{
InitializeComponent();
DatabaseConnection = DependencyService.Get<IConnection>().GetConnection();
CreateTables();
}
private void CreateTables()
{
DatabaseConnection.CreateTable<ObjectInstanceData>();
DatabaseConnection.CreateTable<Models.Objects.String>();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
So, basically the logic should be when no internet, work with sql lite (keep local changes), and when internet come back upload that changes that kept in db, and erase data from tables.
You noticed that Im using response model for api.
So, Im calling from my FavoriteObjectViewModel this:
var response = await ApiServiceProvider.GetObjectInstances(null, true);
and in the ApiServiceProvider:
public static async Task<ObjectInstanceResponseModel> GetObjectInstances(string queryString = null, bool? onlyFavorites = null)
{
HttpResponseMessage response;
response = await apiClient.GetAsync(objectInstancesEndpoint);
var resultContent = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ObservableCollection<ObjectInstanceData>>(resultContent);
objectInstanceResponse.ObjectInstances = result;
//after each api call I need to update db
//delete previous data, and add fresh data from api
App.DatabaseConnection.DeleteAll<ObjectInstanceData>();
foreach (var item in result)
{
App.DatabaseConnection.Insert(item);
if (item.Strings != null && item.Strings.Count > 0)
App.DatabaseConnection.InsertAll(item.Strings);
}
//I only get the data for ObjectInstanceData, Strings model is empty!
var objectsResponseDb = App.DatabaseConnection.GetAllWithChildren<ObjectInstanceData>();
objectInstanceResponse.Succeeded = true;
return objectInstanceResponse;
}
So, my questions are:
If I create tables each time in App.cs is that mean, that I not store data, when user quit application, and re-enter again?
Why is the model Strings empty? When I invoke var strings = App.DatabaseConnection.GetAllWithChildren<Models.Objects.String>(); I can see that there is data?
What is the best approach for doing offline "logging"? maybe there is a some better nuget for sqllite?
I don't know much about persistent databases but one thing I can tell you for sure:you're approach is wrong.
You should create a separate class for the database's logic like creating tables and instantiating the connection along with other methods for manipulating data.
In the App class you can create a static resource of the database class which you can call it and make use of the methods in it.
I'm not very good with explaining but here it is a very basic example of how it should look:https://learn.microsoft.com/en-us/xamarin/get-started/tutorials/local-database/?tutorial-step=2&tabs=vswin.
I have implemented local DB in my project and I am using the following code to select all the items from local DB having a particular webContentId.
public List<Messages> GetAllItemsByWebContentId(string webContentId)
{
lock (locker)
{
return database.Table<Messages>().Where(o => o.webContentDefinitionId == webContentId).ToList();
}
}
Messages is my model class.
public class Messages
{
public Messages()
{
}
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public int tweetId { get; set; }
public string profileImage { get; set; }
public string name { get; set; }
public long createdTime { get; set; }
public string tweetData { get; set; }
public string mediaUrl { get; set; }
public string webContentDefinitionId { get; set; }
}
Now I need to sort this list in the order of createdTime. My createdTime is a 13 digit java timestamp. One example created time is 1543608245696, which means 01/12/2018 01:34. Without sorting, the latest messages are coming on the last of the local database. So inside of GetAllItemsByWebContentId() how can I add created time sorting?
Very easy!
change your code to:
return database.Table<Messages>()
.Where(o => o.webContentDefinitionId == webContentId)
.OrderByDescending(x => x.CreatedTime)
.ToList();
I'm trying to use the feature documented here :
https://github.com/ServiceStack/ServiceStack.OrmLite#custom-sql-customizations
This is how I'm using it:
var q = Db.From<MemberAccess>().LeftJoin<Member>();
return Db.Select<MemberResponse>(q);
Response object:
public class MemberResponse
{
public Guid Id { get; set; }
public string MemberFirstName { get; set; }
public string MemberLastName { get; set; }
public string MemberEmail { get; set; }
[Default(OrmLiteVariables.SystemUtc)]
public string AccessedOn { get; set; }
[CustomSelect("CONCAT(LEFT(Member.FirstName, 1),LEFT(Member.LastName,1))")]
public string MemberInitial { get; set; }
}
It seems like whatever I put in CustomSelect doesn't get used. Maybe, I'm not using this correctly? Also, the Default attribute doesn't work either.I tried that as it was an example from the doco.
Any idea will be appreciated.
Thanks in advance.
The [CustomSelect] only applies to the source table. Selecting the results in a custom type is used to map the returned resultset on the MemberResponse type, it doesn't have any effect on the query that gets executed.
Likewise with [Default(OrmLiteVariables.SystemUtc)] that's used to define the default value when creating the table which is only used when it creates the Column definition, so it's only useful on the source Table Type.
Both these attributes should only be added on the source MemberAccess to have any effect, which your mapped MemberResponse can access without any attributes, e.g:
public class MemberResponse
{
public Guid Id { get; set; }
public string MemberFirstName { get; set; }
public string MemberLastName { get; set; }
public string MemberEmail { get; set; }
public string AccessedOn { get; set; }
public string MemberInitial { get; set; }
}
Sql.Custom() API
The new Sql.Custom() API added in v4.5.5 that's available on MyGet will let you select a custom SQL Fragment, e.g:
var q = Db.From<MemberAccess>().LeftJoin<Member>()
.Select<MemberAccess,Member>((a,m) => new {
Id = a.Id,
MemberFirstName = m.FirstName,
MemberLastName = m.LastName,
MemberEmail = m.Email,
MemberInitial = Sql.Custom("CONCAT(LEFT(Member.FirstName,1),LEFT(Member.LastName,1))")
});
return Db.Select<MemberResponse>(q);
I want to create a generic repository method for the complex data(Data Result of multiple joins in database). Following are the classes which hold the data . The data comes from SQL is the join of three tables(Tables architecture is same as of class)
public class InterfaceHeaderSetting
{
public Guid Id { get; set; }
public string CodaDocCode { get; set; }
public string Company { get; set; }
public string Currency { get; set; }
public string DocDescription { get; set; }
public Screen Screen { get; set; }
public Interface Interface { get; set; }
}
public class Screen
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Interface
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I have the repository methods like
public IEnumerable<T> GetAllDynamic(string sql)
{
return Connection.Query<T>(sql, commandType: CommandType.StoredProcedure).ToList();
}
public T Update(T entity, string sql, object dynamicParameters)
{
return
Connection.Query<T>(sql, dynamicParameters, commandType: CommandType.StoredProcedure).SingleOrDefault();
}
I want one more repository method by which I can fill the objects like InterfaceHeaderSetting object.
I don't think you should reinvent the wheel. For this type of function i think Entity Framework is the solution.
In my projects i let entity framework handle more advanced querys that need joins and let dapper do the simple insert, update and select jobs.