使用最新的Entity Framework和Code-First创建数据库模型时遇到了一些问题(详见Entity Framework 4.1 Code First approach to create many-to-many relation)。
与此同时,我已经发现问题不再是实体框架本身,而是与WCF RIA DomainServices一起使用。
为了完整起见 - 这是我的相关代码优先代码:
//
// Models
//
public class Author
{
public Author()
{
this.Books = new Collection<Book>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ID { get; set; }
[MaxLength(32)]
[Required]
public string Name { get; set; }
[Include]
[Association("Author_Book", "ID", "ID")]
public Collection<Book> Books { get; set; }
}
public class Book
{
public Book()
{
// this.Authors = new Collection<Author>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ID { get; set; }
[MaxLength(32)]
[Required]
public string Name { get; set; }
// I really would like to create this navigation property, but there seems to be no way
// to tell my DomainService to include it.
// public Collection<Author> Authors { get; set; }
}
//
// Mappings
//
public class AuthorMapping : EntityTypeConfiguration<Author>
{
public AuthorMapping()
: base()
{
this.HasMany (g => g.Books)
.WithMany(/*m => m.Authors*/)
.Map (gm => gm.ToTable("Author_Book"));
}
}
//
// DbContext
//
public class BookAuthorModelContext : DbContext
{
public BookAuthorModelContext()
: base(@"data source=localhost\MSSQLSERVER2008R2;database=BookAuthor;integrated security=True;")
{
}
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AuthorMapping());
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
//
// DomainService
//
[EnableClientAccess()]
public class BookAuthorDomainService : DomainService
{
static BookAuthorDomainService()
{
Database.SetInitializer<BookAuthorModelContext>(new BookAuthorModelInitializer());
}
public BookAuthorDomainService()
{
this.m_modelContext = new BookAuthorModelContext();
}
public IQueryable<Author> GetAuthors()
{
return this.m_modelContext.Authors.Include("Books");
}
public void InsertAuthor(Author Author)
{
this.m_modelContext.Insert(Author);
}
public void UpdateAuthor(Author Author)
{
this.m_modelContext.Update(Author, this.ChangeSet.GetOriginal(Author));
}
public void DeleteAuthor(Author Author)
{
this.m_modelContext.Delete(Author);
}
public IQueryable<Book> GetBooks()
{
return this.m_modelContext.Books;//.Include("Authors");
}
public void InsertBook(Book Author)
{
this.m_modelContext.Insert(Author);
}
public void UpdateBook(Book Author)
{
this.m_modelContext.Update(Author, this.ChangeSet.GetOriginal(Author));
}
public void DeleteBook(Book Author)
{
this.m_modelContext.Delete(Author);
}
protected override void Dispose(bool disposing)
{
if (disposing)
this.m_modelContext.Dispose();
base.Dispose(disposing);
}
protected override bool PersistChangeSet()
{
this.m_modelContext.SaveChanges();
return base.PersistChangeSet();
}
private BookAuthorModelContext m_modelContext;
}
按预期创建SQL表。在我的客户端应用程序中,我正在使用带有DomainDataSource的RadGridView:
<UserControl>
<UserControl.Resources>
<webServices:BookAuthorDomainContext x:Name="BookAuthorDomainContext"/>
</UserControl.Resources>
<riaControls:DomainDataSource x:Name="AuthorDomainDataSource"
DomainContext="{StaticResource BookAuthorDomainContext}" QueryName="GetAuthorsQuery"
d:DesignData="{d:DesignInstance webModels:Author, CreateList=true}">
<telerik:RadGridView x:Name="AuthorGridView" DataContext="{Binding ElementName=AuthorDomainDataSource}"
ItemsSource="{Binding Data}" IsBusy="{Binding IsBusy}"/>
</UserControl>
现在事情变得有趣了。如果我向空数据库添加两个记录 - 一个到Author表,另一个到Book表 - 那么两个记录'ID'字段都是'1'。有趣的是,包含Books的GetAuthorsQuery()将Book添加到作者的书籍属性中。 创建的Author_Book(join-)表中没有条目。所以,我已经启动了我的SQL-Profiler,看看到底发生了什么。这就是我发现的:
SELECT
[Project1].[ID] AS [ID],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[ID1] AS [ID1],
[Project1].[Name1] AS [Name1]
FROM ( SELECT
[Limit1].[ID] AS [ID],
[Limit1].[Name] AS [Name],
[Join1].[ID] AS [ID1],
[Join1].[Name] AS [Name1],
CASE WHEN ([Join1].[Author_ID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM (SELECT TOP (20) [c].[ID] AS [ID], [c].[Name] AS [Name]
FROM [dbo].[Author] AS [c] ) AS [Limit1]
LEFT OUTER JOIN (SELECT [Extent2].[Author_ID] AS [Author_ID], [Extent3].[ID] AS [ID], [Extent3].[Name] AS [Name]
FROM [dbo].[Author_Book] AS [Extent2]
INNER JOIN [dbo].[Book] AS [Extent3] ON [Extent3].[ID] = [Extent2].[Book_ID] ) AS [Join1] ON [Limit1].[ID] = [Join1].[Author_ID]
) AS [Project1]
ORDER BY [Project1].[ID] ASC, [Project1].[C1] ASC
为什么他这样做?我真的很想使用我的多对多关系,但我也很乐意使用单向关系(至少会有一些工作)。
提前感谢您的帮助。
答案 0 :(得分:0)
我不使用属性,而是使用地图。从来没有,我希望你会觉得它很有用。
这就是我如何在作者和书籍之间写出多对多的关系,并且能够从作者和副作者那里访问一本书。
以下是一个完整的示例,您可以复制并粘贴和编译。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WordAndImages.Entities;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.ComponentModel.DataAnnotations;
namespace Bookstore
{
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
public Author()
{
Books = new List<Book>();
}
}
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Book()
{
Authors = new List<Author>();
}
}
public class Context : DbContext
{
static Context()
{
Database.SetInitializer<Context>(null);
}
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AuthorMap());
modelBuilder.Configurations.Add(new BookMap());
}
}
public class BookMap : EntityTypeConfiguration<Book>
{
public BookMap()
{
this.HasMany(t => t.Authors)
.WithMany(a => a.Books)
.Map(t => t.ToTable("authorsbooks").MapLeftKey("book_id").MapRightKey("author_id"));
}
}
public class AuthorMap : EntityTypeConfiguration<Author>
{
public AuthorMap()
{
this.HasMany(a => a.Books)
.WithMany(b => b.Authors)
.Map(t => t.ToTable("authorsbooks").MapLeftKey("author_id").MapRightKey("book_id"));
}
}
class Program
{
static void Main(string[] args)
{
#region Saving
var context = new Context();
context.Database.Delete();
context.Database.CreateIfNotExists();
var book1 = new Book { Title = "Joy" };
var book2 = new Book { Title = "Happy" };
var author1 = new Author { Name = "Lisa" };
var author2 = new Author { Name = "John" };
var author3 = new Author { Name = "Luca" };
book1.Authors.Add(author1);
book1.Authors.Add(author2);
book2.Authors.Add(author1);
book2.Authors.Add(author3);
context.Books.Add(book1);
context.Books.Add(book2);
context.SaveChanges();
#endregion
#region Accessing a book from it's author and viceversa
var context2 = new Context();
var recovered_book1 = context2.Books.Where(b => b.Title == "Joy").FirstOrDefault();
Console.WriteLine(string.Format("Book1 has title {0} and has {1} authors", recovered_book1.Title, recovered_book1.Authors.Count));
foreach (var author in recovered_book1.Authors)
Console.WriteLine(author.Name);
var recovered_book2 = context2.Books.Where(b => b.Title == "Joy").FirstOrDefault();
Console.WriteLine(string.Format("Book2 has title {0} and has {1} authors", recovered_book2.Title, recovered_book2.Authors.Count));
foreach (var author in recovered_book1.Authors)
Console.WriteLine(author.Name);
var recovered_author1 = context2.Authors.Where(a => a.Name == "Lisa").FirstOrDefault();
Console.WriteLine(string.Format("{0} wrote {1} books", recovered_author1.Name, recovered_author1.Books.Count));
foreach (var book in recovered_author1.Books)
Console.WriteLine(book.Title);
Console.ReadLine();
#endregion
}
}
}
从数据库中恢复图书时,它会运行此查询
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title]
FROM [dbo].[Books] AS [Extent1]
WHERE N'Joy' = [Extent1].[Title]
当它恢复(延迟加载)其作者时,它会运行
exec sp_executesql N'SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Name] AS [Name]
FROM [dbo].[authorsbooks] AS [Extent1]
INNER JOIN [dbo].[Authors] AS [Extent2] ON [Extent1].[author_id] = [Extent2].[Id]
WHERE [Extent1].[book_id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1