日常业务中经常会有多对多的业务模型,在silverlight开发中由于使用了RIA技术,在EF多对多处理上还是有些地方需要注意,首先看代码:
public partial class TUSER { public string SID { get; set; } public string SNAME { get; set; } [Include] [Association("131231","SID","SID")] public virtual ICollection<TROLE> TROLEs { get; set; } }
public partial class TROLE { public string SID { get; set; } public string SNAME { get; set; } [Include] [Association("11233","SID","SID")] public virtual ICollection<TUSER> TUSERs { get; set; } }
以上是一个最典型的例子,用户角色关系,一个用户可以包含多个角色,一个角色可以包含多个用户,所以在这两个实体中都加入了ICollletion的集合,由于采用了RIA的模式,需要在这两个集合上标注[Include][Association]属性,否则会发现在出现的代理类中无法找到这两个集合,在Association的描述中,第一个参数可以随便起,而第二个参数是本实体用于关联的属性名称,第三个参数是对应的实体中用于关联的属性字段名称。
在这个关系中通常情况下我们还需引入另外一个实体TUSERROLE
public partial class TUSERROLE { public string SUSERID { get; set; } public string SROLEID { get; set; } public string SID { get; set; } }
接下来是定义各自的持久化定义,采用配置map的方式
public class TUSERMap : EntityTypeConfiguration<TUSER> { public TUSERMap() { // Primary Key this.HasKey(t => t.SID); // Properties this.Property(t => t.SID) .IsRequired() .HasMaxLength(38); this.Property(t => t.SNAME) .HasMaxLength(20); // Table & Column Mappings this.ToTable("TUSER", "EF"); this.Property(t => t.SID).HasColumnName("SID"); this.Property(t => t.SNAME).HasColumnName("SNAME"); } }
public class TROLEMap : EntityTypeConfiguration<TROLE> { public TROLEMap() { // Primary Key this.HasKey(t => t.SID); // Properties this.Property(t => t.SID) .IsRequired() .HasMaxLength(38); this.Property(t => t.SNAME) .HasMaxLength(38); // Table & Column Mappings this.ToTable("TROLE", "EF"); this.Property(t => t.SID).HasColumnName("SID"); this.Property(t => t.SNAME).HasColumnName("SNAME"); } }
接下来是重点,如何配置多对多关系,可以再Context中的OnModelCreating建立
public partial class Context : DbContext { static Context() { Database.SetInitializer<Context>(null); } public Context() : base("Name=Context") { } public Context(string conn) : base(EFTracingUtil.GetConnection(conn), true) { var ctx = ((IObjectContextAdapter)this).ObjectContext; ctx.GetTracingConnection().CommandExecuting += (s, e) => { //NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); //log.Info(Environment.NewLine + e.ToTraceString().TrimEnd()); Console.WriteLine(e.ToTraceString()); Console.WriteLine("====================="); }; } public DbSet<TROLE> TROLEs { get; set; } public DbSet<TUSER> TUSERs { get; set; } public DbSet<TUSERROLE> TUSERROLEs { get; set; } EFTracingConnection GetTractingConnection() { var ctx = ((IObjectContextAdapter)this).ObjectContext; return ctx.UnwrapConnection<EFTracingConnection>(); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new TROLEMap()); modelBuilder.Configurations.Add(new TUSERMap()); modelBuilder.Configurations.Add(new TUSERROLEMap()); modelBuilder.Entity<TUSER>().HasMany(b => b.TROLEs) .WithMany(c => c.TUSERs) .Map ( m => { m.MapLeftKey("SUSERID"); m.MapRightKey("SROLEID"); m.ToTable("TUSERROLE", "EF"); }); } }上面是完整的Context,为了能够验证后台SQL操作,其中加入了日志功能,为了简单测试,取消了写日志,而采用Console输出,下面是重点的配置,描述TUSERROLE的左右外联,通过这样配置EF将会知道如何对应user和roles两个实体
modelBuilder.Entity<TUSER>().HasMany(b => b.TROLEs) .WithMany(c => c.TUSERs) .Map ( m => { m.MapLeftKey("SUSERID"); m.MapRightKey("SROLEID"); m.ToTable("TUSERROLE", "EF"); }); }看测试代码,首先测试添加功能,由于没有数据所以先添加用户,角色,重点是下面的这个语句,实现了添加关系表记录
u1.TROLEs.Add(r1)
static void AddUserFromRole() { using (Context db = new Context("name=Context")) { var u1=db.TUSERs.Add(new TUSER() { SID = "U1", SNAME = "用户1" }); var r1 = db.TROLEs.Add(new TROLE() { SID = "R1", SNAME = "角色1" }); try { u1.TROLEs.Add(r1); db.SaveChanges(); Console.ReadKey(); } catch(System.Exception se) { Console.WriteLine(se.InnerException.Message); Console.ReadKey(); } } }
看看运行结果,从图上可以看出已经在添加了tuser,trole,以及tuserrole各一条记录,
下面再来看看查询,下面是从一个角色中查询,可以得到该角色的同时能够获得该角色下的所有用户列表
static void SelectUserFromRole() { using (Context db = new Context("name=Context")) { var r1 = db.TROLEs.Include("TUSERs").FirstOrDefault(p => p.SID == "R1"); Console.ReadKey(); } }
看看结果:
EF为我们自动产生了这个复杂的查询
接下来在来看看删除
1、删除关系,即删除角色下的一个用户,或删除用户下的一个角色,正常情况下TUSERROLE将会减少一条记录
static void DelUserFromRole() { using (Context db = new Context("name=Context")) { var u1 = db.TUSERs.FirstOrDefault(p => p.SID == "U1"); var r1 = db.TROLEs.FirstOrDefault(p => p.SID == "R1"); u1.TROLEs.Remove(r1); try { db.SaveChanges(); Console.ReadKey(); } catch (System.Exception se) { Console.WriteLine(se.InnerException.Message); Console.ReadKey(); } } }
这段代码将会从系统中找出U1用户,以及R1角色,然后我们需要将R1角色从U1中移除,如果U1的角色列表中无此角色则不会移除,如果有则会删除,看结果:
2、带级联删除的方式,重点是查询的时候加入Include,其中的参数为该实体的集合名称
static void DelUserCastle() { using (Context db = new Context("name=Context")) { var u1 = db.TUSERs.Include("TROLEs").FirstOrDefault(p => p.SID == "U1"); db.TUSERs.Remove(u1); try { db.SaveChanges(); Console.ReadKey(); } catch (System.Exception se) { Console.WriteLine(se.InnerException.Message); Console.ReadKey(); } } }看结果:
以上代码以及完成了EF中多对多情况下的关联的查询,添加、删除功能,需要提示的是本人采用的是oracle数据库,所以在totable中需要加入DbSchema。
总结:1、多对多关系的重点是在构建关联,可以将其中两个相联系的实体中的一个利用多联,建立与关联表之间的关系。然后Ef会帮助我们处理这类关系。
2、在silverlight 中才用的是RIA模式,所以需要在实体类中标注属性标签否则无法在代理类中获得实体集合
3、在使用查询或删除时使用Include将会实现级联删除