© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
LINQ via C# 3.0
Chapter 6 – LINQ to SQL
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
LINQ to Relational Data
•
LINQ to DataSet
–
Leveraging the existing investment
•
LINQ to SQL
–
Designer-generated mapping
–
Custom mapping, ORM facilities
•
LINQ to Entities – VS2008 SP1
•
ADO.NET Data Services – VS2008 SP1
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
LINQ to DataSet
•
DataSets and DataTables do not
implement IEnumerable
–
You could roll your own
•
LINQ provides an AsEnumerable()
extension for DataSets in
System.Data.DataSetExtensions.dll
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
LINQ to DataSet
DataSet dataSet = new DataSet("Library");
dataSet.ReadXml(libraryStream);
var books = dataSet.Tables[0].AsEnumerable();
var authors =
from book in books
select book.Field<string>("author");
Array.ForEach(authors.ToArray(),
Console.WriteLine);
•
LINQ to DataSet can be used as an object-based
LINQ to XML!
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
LINQ to SQL
•
The primary challenge:
–
Abstracting away the data source
•
DataSets are filled and queried in a non-
standard way
•
But LINQ to Objects != DB access
–
Retrieve 15 rows from a 1,000,000-row table
–
Retrieve items based on DB indexes
–
Support DB-paging, partitioning, views, sprocs, ...
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
IQueryable
•
LINQ to SQL isn’t based on IEnumerable
•
Instead, Table<T> implements IQueryable
–
Contains an Expression Tree
•
This expression tree is parsed by the query
provider at runtime
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Data Source Abstraction
•
We still use the same LINQ keywords and
query operators
–
The query provider translates them into something else
and executes them
–
SQL, Web Service calls, REST URLs, ...
from student in students
select new {
student.Name,
Average =
student.Grades.Average(
g => g.Value)
}
SELECT [t0].[Name], (
SELECT AVG([t1].[Value])
FROM [dbo].[Grades] AS [t1]
WHERE [t1].[StudentId] =
[t0].[Id]
) AS [Average]
FROM [dbo].[Students] AS [t0]
LINQ to SQL
Provider
students is
IQueryable<Student>
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Mapping Data to Objects
•
LINQ to Objects:
–
Data = objects
•
LINQ to SQL:
–
Data = normalized relational database tables
–
Objects = business entities, DAL, data contracts, ...
•
System.Data.Linq.Mapping.MappingSource
–
AttributeMappingSource
–
XmlMappingSource
LINQ to SQL is an
Object-Relational
Mapper
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Attribute-Based Mapping
[Table(Name=“Courses”)]
public class Course
{
[Column(IsPrimaryKey=true]
public int Id {get;set;}
[Column(...)]
public string Name {get;set;}
}
[Table(Name=“Students”)]
public class Student
{
[Column(IsPrimaryKey=true]
public int Id {get;set;}
[Column(...)]
public string Name {get;set;}
}
[Table(Name=“Grades”)]
public class Grades
{
[Column(IsPrimaryKey=true]
public int Id {get;set;}
[Column(...)]
public int Value {get;set;}
[Association(...)]
public EntityRef<Student> Student ...
[Association(...)]
public EntityRef<Course> Course ...
}
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
The Data Context
•
The DataContext class is the LINQ to
SQL orchestrator
using (DataContext context =
new DataContext(@"D:\Temp\CourseManagement.mdf")) {
context.Log = Console.Out;
var product =
from student in context.GetTable<Student>()
from course in context.GetTable<Course>()
select new { Student=student, Course=course };
Table<Grade> grades = context.GetTable<Grade>();
int lastGradeId =
(from grade in grades
select (int?)grade.Id).Max() ?? 0;
Connection string,
mapping source
Obtain data
source
Table<T> is
IQueryable
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
DataContext Notes
•
DataContext is not just a portal to the
entities
•
It tracks changes and detects conflicts
•
It guarantees object identity
•
It is decoupled from the mapping source
•
It is lightweight: One per method scope
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
What’s Supported?
•
All language-integrated query operators
•
Most standard query operators
•
Inserts, updates and deletes
•
Custom SQL commands (strings)
•
Database creation from mapping
•
Transactional work
•
Preloading data (local queries)
•
Optimistic locking (concurrency control)
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Updating Data
using (DataContext context...) {
var toUpdate =
from student in context.GetTable<Student>()
where student.GetTable<Grade>()
.Average(g=> g.Value) < 90
select student;
foreach (Student student in toUpdate)
student.Grades.First().Value += 20;
context.SubmitChanges();
}
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Inserting Data
using (DataContext context...) {
Student alex = new Student { Id = 49, Name = "Alex" };
context.GetTable<Student>().InsertOnSubmit(alex);
//New student gets an automatic 100 in first two courses
int lastGradeId = context.GetTable<Grade>().Max(g => g.Id);
foreach (Course course in context.GetTable<Course>().Take(2))
context.Grades.InsertOnSubmit(new Grade {
Id = ++lastGradeId,
Course = course,
Student = alex,
Value = 100
});
context.SubmitChanges();
}
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Deleting Data
using (DataContext context...) {
foreach (Student student in
context.GetTable<Student>().Where(
s => s.Grades.Average(g => g.Value) < 80)) {
//Manual cascading delete
foreach (Grade grade in
context.GetTable<Grade>().Where(
g => g.StudentId == student.Id)) {
context.GetTable<Grade>().DeleteOnSubmit(grade);
}
context.GetTable<Student>().DeleteOnSubmit(student);
}
context.SubmitChanges();
}
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Conflict Management
•
Optimistic locking allows for conflict control
when examining and submitting changes
using (CourseManagementDataContext context...) {
ChangeSet changeSet = context.GetChangeSet();
//changeSet.Deletes, changeSet.Inserts, changeSet.Updates
try {
context.SubmitChanges(ConflictMode.ContinueOnConflict);
} catch (ChangeConflictException) {
foreach (var conflict in context.ChangeConflicts) {
conflict.Resolve(RefreshMode.KeepCurrentValues);
foreach (var memberConflict in conflict.MemberConflicts)
memberConflict.Resolve(newValue);
}
}
}
Expensive: Checks all members
Use a timestamp or a version field
[Column(IsVersion=true, …)]
Or avoid altogether
[Column(UpdateCheck=UpdateCheck.Never, …)]
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Transactional Work
•
Just like ADO.NET, DataContexts
participate in transactions
using (TransactionScope scope = new TransactionScope())
using (DataContext context...) {
context.GetTable<Student>().InsertOnSubmit(new Student {
Id=12, Name="Josh"});
context.SubmitChanges();
//Without scope.Complete(), the changes are not committed
}
using (CourseManagementDataContext context...) {
Console.WriteLine("Student exists? " +
context.GetTable<Student>().Any(s => s.Name == "Josh"));
}
Ambient transaction
scope
© Copyright SELA Software & Education Labs Ltd. 14-18 Baruch Hirsch St. Bnei Brak 51202 Israel
Explicit Transaction
•
Transactions can be performed explicitly
–
Not recommended if distributed transactions are
required
using (CourseManagementDataContext context...) {
context.Connection.Open();
context.Transaction =
context.Connection.BeginTransaction();
context.GetTable<Student>().InsertOnSubmit(new Student {
Id = 12, Name = "Josh“ });
context.SubmitChanges();
context.Transaction.Rollback();
}