Is there a way to do a shorthand insert with dapper, without specifying all of the columns? I just want to pass in either a single object or a list of objects. All of my model names match my table names in the db.
I am creating a function that copies one entity from another and don't want to specify columns because code management will be minimal if adding another field in the future.
i.e.
StringBuilder sql = new StringBuilder();
sql.AppendLine("SELECT *");
sql.AppendLine("FROM Product ");
sql.AppendLine("WHERE Id = #Id");
Product source = connection.Query<Product>(sqlCopy.ToString(),
new
{
Id = productId
}, transaction).SingleOrDefault();
// INSERT source entity here without specifying INSERT INTO (COLUMNS)
Have you tried using Dapper.SimplerCRUD (https://github.com/ericdc1/Dapper.SimpleCRUD) or Dapper.Contrib (https://github.com/StackExchange/Dapper/tree/master/Dapper.Contrib)?
Insert Dapper.SimplerCRUD (from github example):
public static int Insert(this IDbConnection connection, object entityToInsert)
Example usage:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
//Additional properties not in database
[Editable(false)]
public string FullName { get { return string.Format("{0} {1}", FirstName, LastName); } }
public List<User> Friends { get; set; }
[ReadOnly(true)]
public DateTime CreatedDate { get; set; }
}
var newId = connection.Insert(new User { FirstName = "User", LastName = "Person", Age = 10 });
Contrib (from github example):
Insert methods
Insert one entity
connection.Insert(new Car { Name = "Volvo" });
or a list of entities.
connection.Insert(cars);
Related
Consider an Sqlite database, whose partial schema is shown below (we are not considering the Book_Tag table here). Note the many-to-many relationship between media items and tags using the link table Media_Tag:
An object model for these tables is as follows:
public enum MediaType
{
Dvd,
BluRay,
Cd,
Vhs,
Vinyl,
Other
}
public class MediaItem
{
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public string name { get; set; }
}
currently, Dapper is being used to read from the Media table, but without considering tags. The code is as follows:
public IEnumerable<MediaItem> readAll()
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media;";
return db.Query<MediaItem>(sql);
}
}
public MediaItem readById(int id)
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media WHERE id = #id;";
var #params = new { id = id };
return db.Query<MediaItem>(sql, #params).First();
}
}
How to change this so that the tag property of MediaItem is considered when creating the objects, for both cases (read by id and read all rows from the table)? Is a join query required? I'm sure Dapper has a way of doing this nicely, but I don't know how it's done.
You are not interested in anything from the link table so something like this SQL should do:
SELECT M.Id, M.title, M.type, M.Number, M.image, M.runningTime, M.releaseYear, T.Id, T.Name FROM Media as M
INNER JOIN Media_Tag AS MT ON M.id = MT.mediaId
INNER JOIN Tags AS T ON T.id = MT.tagId
If SqLite allows you can use M.*, T.* instead.
I have taken the liberty to add Id properties to your entity classes. I think you are going to need it, otherwise all your tags will be different instead of being unique. You might make it work without it, but it should make your life easier.
public class MediaItem
{
public int Id { get; set; } // New
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public int Id { get; set; } // New
public string name { get; set; }
}
Since both your entity classes have a unique id, you will have to pick them up and make sure they are unique going through the results. We do that by using dictionaries to keep them. I'm only showing the ReadAll, you should be able to do ReadById accordingly.
string sql = "<see above>";
using (var db = new SqliteConnection(this.connectionString))
{
var mediaDictionary = new Dictionary<int, Media>();
var tagDictionary = new Dictionary<int, Tag>();
var list = db.Query<Media, Tag, Media>(
sql,
(media, tag) =>
{
Media mediaEntry;
if (!mediaDictionary.TryGetValue(media.Id, out mediaEntry))
{
// Haven't seen that one before, let's add it to the dictionary
mediaEntry = media;
mediaDictionary.Add(mediaEntry.Id, mediaEntry);
}
Tag tagEntry;
if (!tagDictionary.TryGetValue(tag.Id, out tagEntry))
{
// Haven't seen that one before, let's add it to the dictionary
tagEntry = tag;
tagDictionary.Add(tagEntry.Id, tagEntry);
}
// Add the tag to the collection
mediaEntry.Tags.Add(tagEntry);
return mediaEntry;
},
splitOn: "Id") // This default and could be omitted
.Distinct()
.ToList();
I have a class AnalysisRule
public class AnalysisRule
{
public long Id { get; set; }
public Analysis Analysis { get; set; }
public AnalysisCategory AnalysisCategory { get; set; }
public Gender Gender { get; set; }
public bool FatherHerdBookRequired { get; set; }
public bool MotherHerdBookRequired { get; set; }
public List<Breed> AllowedBreeds { get; set; }
}
That has a list of Breeds
public class Breed
{
public long BreedId { get; set; }
public long AnimalTypeId { get; set; }
public long BreedCode { get; set; }
public string BreedName { get; set; }
public string BreedAcronym { get; set; }
}
This is a many to many relationship that I bind together with a DB table
AnalysisRulesBreeds
Breeds
And AnalysisRules
With Dapper I have tried
var sql = #"select *
from ""AnalysisRules""
join ""AnalysisCategory"" on ""AnalysisRules"".""AnalysisCategoryId"" = ""AnalysisCategory"".""Id""
join ""Analysis"" on ""AnalysisRules"".""AnalysisId"" = ""Analysis"".""Id""
left join ""AnalysisRulesBreeds"" on ""AnalysisRulesBreeds"".""AnalysisRuleId"" = ""AnalysisRules"".""Id""
left join ""Breed"" on ""AnalysisRulesBreeds"".""BreedId"" = ""Breed"".""BreedId""
where ""AnalysisId"" = :AnalysisId";
rules = sqlConnection.QueryAsync<AnalysisRule, AnalysisCategory, Analysis, Breed, AnalysisRule>(
sql,
(ar, c, a, b) =>
{
ar.AnalysisCategory = c;
ar.Analysis = a;
ar.Breeds.Add(b);
return ar;
},
new
{
AnalysisId = analysisId
},
splitOn:"BreedId");
Which gives me
´When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id
Parameter name: splitOn
If I run the same query in SQL Developer I get 2 rows out with same Id but with different data in Breed, so the query should be good enough.
So how do I get these 2 rows into one AnalysisRule entity where Breeds consist of 2 Breed entities?
EDIT
I now have
sqlConnection.Open();
var sql = #"select ar.*,
ac.*,
b.*
from ""AnalysisRules"" ar
join ""AnalysisCategory"" ac on ar.""AnalysisCategoryId"" = ac.""Id""
join ""Analysis"" a on ar.""AnalysisId"" = a.""Id""
left join ""AnalysisRulesBreeds"" on ""AnalysisRulesBreeds"".""AnalysisRuleId"" = ar.""Id""
left join ""Breed"" b on ""AnalysisRulesBreeds"".""BreedId"" = b.""Id""
where ""AnalysisId"" = :AnalysisId";
var rules = sqlConnection.QueryAsync<AnalysisRule, AnalysisCategory, Analysis, Breed, AnalysisRule>(
sql,
(ar, c, a, b) =>
{
ar.AnalysisCategory = c;
ar.Analysis = a;
ar.Breeds.Add(b);
return ar;
},
new
{
AnalysisId = analysisId
});
return await rules;
Removed the splitOn, changed AnalysisRulesBreedsId to Id but I still get
When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id
Parameter name: splitOn
If I do the same query in SQLDev I get
By selecting * you get the columns of every joined table. Also you set splitOnto BreedId. Now Dapper expects that to separate the row columns of one joined table from the next, it should look for a column named BreedId.
This does not work because all tables except AnalysisRulesBreeds use Id as id column name.
Try removing the splitOn parameter, then it will default to Id. Then adjust your select-clause to only select from the tables you actually need in the result, eg.
select AnalysisRule.*, AnalysisCategory.*, Analysis.*, Breed.*
(assuming that your Analysis table and AnalysisCategory table follow the convention of having an Id column named ´Id´).
I have a problem when I am updating data to database. When I want to update data, Entitiy Framework adds new rows to tables that can have multiple rows (tables that have foreign key).
Database model:
When I update Phone/Contact or Tags entity, Entity Framework automatically adds new row instead of updating it
Here is code that I used:
public string UpdateContact(Contact contact)
{
if (contact != null)
{
int id = Convert.ToInt32(contact.id);
Contact Updatecontact = db.Contacts.Where(a => a.id == id).FirstOrDefault();
Updatecontact.firstname = contact.firstname;
Updatecontact.lastname = contact.lastname;
Updatecontact.address = contact.address;
Updatecontact.bookmarked = contact.bookmarked;
Updatecontact.city = contact.city;
Updatecontact.notes = contact.notes;
Updatecontact.Emails1 = contact.Emails1;
Updatecontact.Phones1 = contact.Phones1;
Updatecontact.Tags1 = contact.Tags1;
db.SaveChanges();
return "Contact Updated";
}
else
{
return "Invalid Record";
}
}
EDIT:
Here is EF Model code:
Contact:
public partial class Contact
{
public Contact()
{
this.Emails1 = new HashSet<Email>();
this.Phones1 = new HashSet<Phone>();
this.Tags1 = new HashSet<Tag>();
}
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
public string city { get; set; }
public Nullable<byte> bookmarked { get; set; }
public string notes { get; set; }
public virtual ICollection<Email> Emails1 { get; set; }
public virtual ICollection<Phone> Phones1 { get; set; }
public virtual ICollection<Tag> Tags1 { get; set; }
}
Emails/Tags and Phone have same model (with different name for value)
public partial class Email
{
public int id { get; set; }
public int id_contact { get; set; }
public string email1 { get; set; }
public virtual Contact Contact1 { get; set; }
}
Update properties rather than set new objects.
Updatecontact.Emails1.email1 = contact.Emails1.email1;
Updatecontact.Phones1.number = contact.Phones1.number;
Updatecontact.Tags1.tag1 = contact.Tags1.tag1;
Edit: seems that your contact model has lists of emails, phones and tags. If this is so, then simple assignment won't work. Instead, when sent from the client, you have to find one-by-one and update:
foreach ( var email in contact.Emails1 )
{
// first make sure the object is retrieved from the database
var updateemail = Updatecontact.Emails1.FirstOrDefault( e => e.id == email.id );
// then update its properties
updateemail.email1 = email.email1;
}
// do the same for phones and tags
It's doing that because you're setting the different HashSet values to the values of a completely different collection, namely from what you call contact in that method. In order for you to properly do an update, you're going to have to loop through the emails, phones, and tags to check if those need to be added/updated/deleted on the actual object that you're trying to update.
First, why do you have to search for the contact if you are already receiving it by parameter? That makes me think that you are creating a new one because you are in a different context, if so, then it creates a new record because you have 2 different object in 2 different context.
Try using just one object in the same context to update, EF should mark the object to modification by itself, if not then try making sure before saving that your object has EntityState.Modified.
public class User
{
public int ID { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
}
class TestDbContext : DbContext
{
public TestDbContext()
: base("DefaultConnectionString")
{
}
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
}
Above are the model definitions and DbContext difinitions. I want to add a new address for the user, so i wrote my code as bellow:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
user.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
My doubt is why there are 3 SQL queries are executed in this code?
It's generated in FirstOrDefault
SELECT TOP (1)
[Extent1].[ID] AS [ID],
[Extent1].[EmailAddress] AS [EmailAddress]
FROM [dbo].[Users] AS [Extent1]
WHERE 1 = [Extent1].[ID]
It's generated in user.Addresses.Add
exec sp_executesql N'SELECT
[Extent1].[ID] AS [ID],
[Extent1].[City] AS [City],
[Extent1].[Street] AS [Street],
[Extent1].[Postcode] AS [Postcode],
[Extent1].[User_ID] AS [User_ID]
FROM [dbo].[Addresses] AS [Extent1]
WHERE ([Extent1].[User_ID] IS NOT NULL)
AND ([Extent1].[User_ID] = #EntityKeyValue1)',N'#EntityKeyValue1 int',#EntityKeyValue1=1
It's generated in SaveChanges
exec sp_executesql N'INSERT [dbo].[Addresses]([City], [Street], [Postcode], [User_ID])
VALUES (#0, #1, #2, #3)
SELECT [ID]
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [ID] = scope_identity()',N'#0 nvarchar(max) ,#1 nvarchar(max) ,#2 nvarchar(max) ,#3 int',#0=N'City',#1=N'Street',#2=N'Postcode',#3=1
How can I avoid the second SQL?
The Addresses nav property is lazy loading when you access the property (i.e. user.Addresses), which is why you're getting the second SQL command.
Try disabling lazy loading and see if that works (don't forget to initialize the Addresses property in a constructor for User e.g.:
public User()
{
Addresses = new HashSet<Address>();
}
You can even prevent the first two queries!
You already know the user's ID value, so all you have to do is set the foreign key value in Address. Of course, Address should have this property:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public int UserID { get; set; } // Set this property
public User User { get; set; }
}
The pair User and UserID is called a foreign key association, which is the preferred way to deal with associations in EF (precisely because it can reduce the number of queries).
Have you tried changing the class definition slightly:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public virtual User User { get; set;}
}
so that now you can write:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
context.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
User = user
});
context.SaveChanges();
As already pointed out the problem here is your Addresses property is a navigation property so when you access it EF is generating a SELECT statement to load the collection in. To avoid this from happening you have 2 options:
Eager load the addresses when you load the User so you take the hit when you first load the user e.g. Users.Include(x => x.Addresses)
Disable lazy loading on that particular property by making the Addresses property non-virtual
I would add a UserId foreign key to the Address class, then I'd do this:
var context = new TestDbContext();
context.Addresses.Add(new Address()
{
UserId = 1,
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
No need to retrieve the user or the user's existing addresses
Foreign keys make Entity Framework is easier to use:
Why does Entity Framework Reinsert Existing Objects into My Database?
Making Do with Absent Foreign Keys
And relationship fix-up will synchronise the navigation property:
http://msdn.microsoft.com/en-gb/data/jj713564.aspx
I am using the following query. It returns 5 rows in a list but all the elements in the list are null.
public class TempClass
{
public int SID { get; set; }
public string SNAME { get; set; }
public string SAGE { get; set; }
}
var i = _dbContext.Database
.SqlQuery<TempClass>("select ID, NAME, AGE from eis_hierarchy")
.ToList();
What I am doing wrong?
Try making the columns returned in the SQL query match the property names in the class you're trying to return:
var i = _dbContext.Database
.SqlQuery<TempClass>("select ID as SID, NAME as SNAME, AGE as SAGE from eis_hierarchy")
.ToList();