Cascading insert with Entity Framework - c#

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

Related

Entity framework produces left join when condition on foreign key

I have 2 models:
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Email { get; set; }
[Required]
[MaxLength(100)]
public string Password { get; set; }
}
and
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public User User { get; set; }
}
I would like to use this query to retrieve all questionnaires of certain user:
List<Questionnaire> questionnaires = this._dbContext.Questionnaires.Where(a => a.User.Id == 1).ToList();
It works, but entity framework produces this sql query:
SELECT `q`.`Id`, `q`.`Title`, `q`.`UserId`
FROM `Questionnaires` AS `q`
LEFT JOIN `Users` AS `u` ON `q`.`UserId` = `u`.`Id`
WHERE `u`.`Id` = 1;
In my opinion, left join is unnecessary. Please is there any workaround to avoid this left join? Thank you in advance.
You will need to expose UserId property on Questionnaire manually:
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
And use it in query instead of a.User.Id:
var questionnaires = this._dbContext.Questionnaires
.Where(a => a.UserId == 1) // use UserId instead of User.Id
.ToList();
For more information:
If you choose not to explicitly include a foreign key property in the dependant end of the relationship, EF Core will create a shadow property using the pattern Id. If you look at the Questionnaire database table, UserId column exists and it has created by EF core as a shadow foreign key.
When you refer User inside where clause _dbContext.Questionnaires.Where(a => a.User.Id == 1), EF Core translate linq query into TSQL left join.
You can also use shadow property do define foreign key:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Questionnaire>()
.Property<int>("UserId");
builder.Entity<Questionnaire>()
.HasOne(e => e.User)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
}
Now left join will be replaced with the inner join:
SELECT [q].[Id], [q].[Title], [q].[UserId]
FROM [Questionnaires] AS [q]
INNER JOIN [Users] AS [c] ON [q].[UserId] = [c].[Id]
WHERE [c].[Id] = 1
To avoid unnecessary join as #Guru Stron said you need to expose UserId property on Questionnaire class.

Mapping multiple complex type properties with Dapper

I have the following Dapper query:
var orderModels = db.Query<OrderModel>(#"
SELECT
o.[Id],
o.[CustomerId],
o.[DeliveryAddress_FirstName],
o.[DeliveryAddress_LastName],
o.[DeliveryAddress_Line1],
o.[DeliveryAddress_Line2],
o.[DeliveryAddress_City],
o.[DeliveryAddress_State],
o.[DeliveryAddress_PostCode],
o.[DeliveryAddress_Country],
o.[BillingAddress_FirstName],
o.[BillingAddress_LastName],
o.[BillingAddress_Line1],
o.[BillingAddress_Line2],
o.[BillingAddress_City],
o.[BillingAddress_State],
o.[BillingAddress_PostCode],
o.[BillingAddress_Country]
FROM
[Order] o
");
And I'd like to load it into a data model of the following structure:
public class OrderModel
{
public int Id { get; set; }
public int CustomerId { get; set; }
public AddressModel DeliveryAddress { get; set; }
public AddressModel BillingAddress { get; set; }
}
public class AddressModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
}
I have tried to look this up, and there is a feature in Dapper called multi-mapping. However, I can't figure out how to use it in my use-case when I'm not joining results from multiple tables.
It feels like a very common scenario for ORM like Dapper, so I'm sure I'm just missing something obvious. If you're more knowledgeable about Dapper, please help.
What is the best way to accomplish this?
Dapper does not care about joins, you just need to provide it correct fields for splitOn options like:
var orderModels = db.Query<OrderModel, AddressModel, AddressModel, OrderModel>(#"
SELECT
o.[Id],
o.[CustomerId],
o.[DeliveryAddress_FirstName] AS [FirstName], // Field names should match properties of your model
o.[DeliveryAddress_LastName] AS [LastName],
o.[DeliveryAddress_Line1] AS [Line1],
o.[DeliveryAddress_Line2] AS [Line2],
o.[DeliveryAddress_City] AS [City],
o.[DeliveryAddress_State] AS [State],
o.[DeliveryAddress_PostCode] AS [PostCode],
o.[DeliveryAddress_Country] AS [Country],
o.[BillingAddress_FirstName] AS [FirstName],
o.[BillingAddress_LastName] AS [LastName],
o.[BillingAddress_Line1] AS [Line1],
o.[BillingAddress_Line2] AS [Line2],
o.[BillingAddress_City] AS [City],
o.[BillingAddress_State] AS [State],
o.[BillingAddress_PostCode] AS [PostCode],
o.[BillingAddress_Country] AS [Country]
FROM
[Order] o
",
(order, deliveryAddress, billingAddress) => {
order.DeliveryAddress = deliveryAddress;
order.BillingAddress = billingAddress;
return order;
},
splitOn: "FirstName,FirstName");
It is explained in this article.
Also, the select's field names have to match model property names for Dapper to figure out the mapping automatically.

Shorthand Insert with Dapper - without specifying columns

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);

Entity Framework projection behaviour

First post on here and quite a simple one.
I've been looking into simplifying some complex queries in an application I'm developing and I'm scratching my head a bit on the below.
So say I have these two classes:
A domain entity "EmailRecipient" (used with EF code-first so expect a SQL table to be generated with the same column names).
public class EmailRecipient
{
public Guid Id { get; set; }
public string FriendlyName { get; set; }
public string ExchangeName { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public string EmailAddress { get; set; }
public string JobTitle { get; set; }
public virtual List<SentEmail> SentEmails { get; set; }
}
and a simple class for JSON serialisation called "EmailLite" defined as
public class EmailLite
{
public string EmailAddress { get; set; }
public Guid Id { get; set; }
public string FriendlyName { get; set; }
}
In my specialised EF6(.1.3) DbContext, I have a DbSet called EmailRecipients.
So naturally executing this linq expression against EmailRecipients
EmailRecipients.Select(x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
});
the generated SQL is
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[EmailAddress] AS [EmailAddress],
[Extent1].[FriendlyName] AS [FriendlyName]
FROM [dbo].[EmailRecipients] AS [Extent1]
So why when I do:
Func<EmailRecipient, EmailLite> projectionFunction = x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
};
EmailRecipients.Select(projectionFunction);
do I get the below (complete) SQL generated:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[FriendlyName] AS [FriendlyName],
[Extent1].[ExchangeName] AS [ExchangeName],
[Extent1].[Surname] AS [Surname],
[Extent1].[Forename] AS [Forename],
[Extent1].[EmailAddress] AS [EmailAddress],
[Extent1].[JobTitle] AS [JobTitle],
[Extent1].[SubscribedOn] AS [SubscribedOn]
FROM [dbo].[EmailRecipients] AS [Extent1]
Any help would be most appreciated!
Cheers,
Sat
IQueryable<T>.Select() takes an Expression<Func<T,TOut>> as parameter, the function you're actually using is IEnumerable<T>.Select() which takes a delegate. Because of this, you are telling EF that from that moment on, you are using IEnumerable rather than IQueryable and the rest of the query will be executed in memory => you're fetching all the columns.
EmailRecipients <-- in memory from here on --> .Select(projectionFunction);
All you need to do is change projectionFunction into an expression and it will work:
Expression<Func<EmailRecipient, EmailLite>> projectionFunction = x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
};

How to add a relationship between tables in LINQ to DB model class

I'm using LINQ to DB (linq2db) and I have a class, Activity.cs which has a Customer property, like this:
public Customer Customer { get; set; }
The Customer class:
public class Customer
{
[PrimaryKey, Identity]
[Column(Name = "CustomerId"), NotNull]
public string Id { get; set; }
[Column(Name = "Name")]
public string Name { get; set; }
}
Now, I want to be able to do something like this:
db.Activities.First().Customer.Name //Returns the customer name of an activity
How can I set the relationship between the entities, such that I can do as explained above?
(Yes, I know it doesn't make sense to have the Id field as a string, I have to work against a messy legacy Access database)
If I good understand, one Activity has one Customer. If so, you should add to your Activity class a relation:
[Table( Name = "Customers" )]
public class Customer
{
[PrimaryKey, Identity]
[Column(Name = "CustomerId"), NotNull]
public string Id { get; set; }
[Column(Name = "Name")]
public string Name { get; set; }
}
[Table( Name = "Activities" )]
public class Activity
{
[PrimaryKey, Identity]
[Column(Name = "ActivityId"), NotNull]
public string Id { get; set; }
[Column( Name = "Customer" )]
private int? customerId;
private EntityRef<Customer> _customer = new EntityRef<Customer>( );
[Association(IsForeignKey = true, Storage = "_customer", ThisKey = "customerId" )]
public Customer Customer{
get { return _customer.Entity; }
set { _customer.Entity = value; }
}
}
A good article about this subject
EDIT:
A walk-around when association does not work:
[Table( Name = "Activities" )]
public class Activity
{
[PrimaryKey, Identity]
[Column(Name = "ActivityId"), NotNull]
public string Id { get; set; }
[Column( Name = "CustomerId" )]
public int? CustomerId;
}
You can retrieve a customer from activity like this:
var activity = db.Activities.FirstOrDefault()
var customer = db.Customers.FirstOrDefault(c => c.Id = activity.CustomerId);
This question is about the use of Linq2db, not of LinqToSQL. Linq2db is an alternative ORM than supports others database types like Postgresql or SQLite.
Linq2db is optimized for not consume many resources, therefore to attempt to directly access the db.Activities.First().Customer.Name, always will throw an exception because it will not load the value of Costomer.
For example, if we use the following code to query the Customers of the Northwind database,
using (var db = new NorthwindDB())
{
var q = from c in db.Customers
select c;
}
It would generate a SQL query like the following:
{-- NorthwindDB SqlServer.2008
SELECT
[t1].[CustomerID],
[t1].[CompanyName],
[t1].[ContactName],
[t1].[ContactTitle],
[t1].[Address],
[t1].[City],
[t1].[Region],
[t1].[PostalCode],
[t1].[Country],
[t1].[Phone],
[t1].[Fax]
FROM
[dbo].[Customers] [t1]
}
Although the Customer entity has a one-to-many relationship with the Order entity they are not going to load these values. For the case that we want to get their Customer Orders, I recommend using an unknown type (or simple class) as the following query.
using (var db = new NorthwindDB())
{
var q = from c in db.Customers
select new
{
c.CompanyName,
EmployeeName = c.Orders.First().Employee.FirstName
};
}
It would generate a SQL query like the following:
{-- NorthwindDB SqlServer.2008
SELECT
[t4].[CompanyName],
[t3].[FirstName] as [FirstName1],
[t3].[c1] as [c11]
FROM
[dbo].[Customers] [t4]
OUTER APPLY (
SELECT TOP (1)
[t1].[FirstName],
1 as [c1]
FROM
[dbo].[Orders] [t2]
LEFT JOIN [dbo].[Employees] [t1] ON [t2].[EmployeeID] = [t1].[EmployeeID]
WHERE
[t4].[CustomerID] = [t2].[CustomerID]
) [t3]
}

Categories

Resources