将一对多公开为一对多/一

时间:2015-11-20 12:59:34

标签: c# entity-framework

我正在尝试现有数据库的模型优先方法。我无法以任何方式改变数据库方案。

此数据库有一组有趣的关系:用户与地址有一对多的关系。但是,地址有一个'IsActive'字段,对于任何给定的用户,只有一个地址是活动的。

我正在寻找一种方法,以便让实体框架理解这一点,以便开发人员在他们的模型中建立关系,好像它是一对一/一关系:user.Addresses.SingleOrDefault(c => c.IsActive).Line1

我需要一个真正的导航属性。我不只是试图避免每次都重复usaCust = customers.Where(c => c.Address.Country == "USA");。实际导航属性允许更复杂的场景,例如从数据库延迟加载或急切加载,或过滤:#Server import threading import socket import math import random import ssl def within(guess,goal,n): absValue = abs(guess - goal) if absValue <= n: return True else: return False #def HandleAdmin(adminSocket): #(conn,addr) = adminSocket.accept() #ts = ssl.wrap_socket(conn, certfile="5cc515_server.crt", #keyfile="5cc515_server.key", #server_side=True, #cert_reqs=ssl.CERT_REQUIRED, #ca_certs="5cc515-root-ca.cer") #if ts.recv(80).decode() == 'Hello\r\n': #ts.send('Admin-Greetings\r\n'.encode()) #if ts.recv(80).decode() == 'Who\r\n': #ts.send((c,a).encode()) #ts.close() #return def HandleClient(c,a): scoreCount = 0 guess = 0 if(c.recv(80).decode()) == 'Hello\r\n': c.send('Greetings\r\n'.encode()) goal = random.randrange(1,21) while guess!= goal: guess =c.recv(80).decode() guess = int(guess[7:len(guess)-2]) if guess == goal: c.send('Correct\r\n'.encode()) elif within(guess, goal, 2) == True: c.send('Close\r\n'.encode()) else: c.send('Far\r\n'.encode()) c.close() return clientSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) clientSocket.bind(("127.0.0.1",4000)) clientSocket.listen(5) adminSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) adminSocket.bind(("127.0.0.1",4001)) adminSocket.listen(5) while True: (c,a) = clientSocket.accept() #handleAdminThread = threading.Thread(target = AcceptAdminConnection, # args = adminSocket) clientThread = threading.Thread(target = HandleClient, args = (c,a)) clientThread.start()

4 个答案:

答案 0 :(得分:1)

您可以简单地将ActiveAddress属性添加到您的实体,并使用它来获取/设置适当的地址为活动状态。

这将有效地为每个用户提供零个或一个ActiveAddresses,可以将其设置为Addresses集合的成员,或设置为null。一次只能激活一个地址。

public class User
{
    [Key]
    public int ID { get; set; }

    public virtual List<Address> Addresses { get; set; }

    [NotMapped]
    public Address ActiveAddress
    {
        get { return Addresses == null ? null : Addresses.Where(t => t.IsActive).FirstOrDefault(); }
        set
        {
            if (Addresses != null)
            {
                //set all addresses inactive
                Addresses.ForEach(t => t.IsActive = false); 
                //set specified address as active if not null                
                if(value != null)
                    value.IsActive = true;
            }
        }
    }
}

public class Address
{
    [Key]
    public int ID { get; set; }

    public string FullAddress { get; set; }

    public bool IsActive { get; set; }

    public int UserID { get; set; }

    [ForeignKey("UserID")]
    public virtual User User { get; set; }
}

要查询此内容,您需要在引用ActiveAddress字段之前枚举并使用Linq to Objects,因为它在db中不存在。如果您的客户和/或地址表中有大量记录,则这可能不可行。

var usaCustomer = c.Customers.ToList().Where(t => t.ActiveAddress.Country == "USA").FirstOrDefault();

要使用Linq to Entities,我目前唯一能想到的方法是通过地址dbset查询你的集合,地址dbset是你想做的事情的倒退,但仍会在db端执行。

var usaCustomer = c.Customers.Where(t => t.Addresses.Any(a => a.IsActive && a.Country == "USA")).FirstOrDefault();

答案 1 :(得分:0)

您打算对地址进行语义验证。您可以为给定查询实现一个期望intovar resultsA = a.GroupJoin(b, master => master.Foo, detail => detail.Foo, (master, details) => new { master.Foo, master.Baz, master.Name, Code = details.Select(detail => detail.Code).First(), Bars = details.Select(detail => new { detail.Value, detail.Code }) }); var resultsB = from master in a join detail in b on master.Foo equals detail.Foo into details select new { master.Foo, master.Baz, master.Name, Code = details.Select(detail => detail.Code).First(), Bars = details.Select(detail => new { detail.Value, detail.Code }) }; DateTime结果的方法,如下所示:

return

您将能够使用它,如下所示:

SingleOrDefault

注意,如果您打算测试其他日期,该方法也支持其他日期。

答案 2 :(得分:0)

一月, 尝试使用动态全局过滤器实体框架

https://github.com/jcachat/EntityFramework.DynamicFilters

答案 3 :(得分:0)

感谢所有讨论和提议的解决方案。他们真的很有帮助,可能对90%有类似问题的人有用。

如果您可以更改数据库而不需要传入参数,E-Bat建议使用适当的关系制作正确的可更新视图,并使用它们。

如果您不需要预先加载,请创建[NonMapped]计算属性或辅助方法。

我们最终得到了一个不同的解决方案,我不能发布我们最终解决方案的所有技术细节,因为它长约1k代码行...

无论如何,我们真的想以一种方式将1-many关系建模为1-1(在给定时间内),以便我们可以急切地加载关系,延迟加载它,或者在数据库查询中使用它(过滤CurrentAddress等)。

首先,我们在Address类型的User上创建了一个属性,并添加了&#39; NotMapped&#39;属性:

public class User{

  public string Name { get; set; }  //simple property

  public virtual ICollection<Address> Addresses { get; set;} //normal navigation collection

  [NotMapped] public virtual Address CurrentAddress { get; set;} //We added this

}

然后,我们看看你通常如何填写这个地址属性:

var users = from user in dbo.Users
            join address in dbo.Addresses.Where(a => a.IsActive)
            on user.Id equals address.UserId
            select new User{
              Name = user.Name
              Addresses = user.Addresses
              CurrentAddress = address
            };

我们制作了一个流畅的API,因此我们可以使用类似于您如何使用EF流利API来定义与模型构建器的正常关系的方式来定义此连接。

this.Entity<User>()
  .HasJITRelationship(u => u.CurrentAddress)
  .From(dbo => dbo.Addresses
  .Where(a => a.IsActive)
  .On(u => u.Id)
  .Equals(a => a.UserId);

最后,假设我们有:

var usaUsers = dbo.Users.Where(c => c.CurrentAddress.Country == "USA");
foreach(var user in usaUsers.ToList()) { //...

只有在调用ToList()时才会执行此IQueryable。此时,usaUsers IQueryable的表达式树包含需要CurrentAddress连接的信息。

我们实际上使我们的DBContext更聪明一些。而不是dbo.Users返回正常的DBSet(),我们实际上返回一个注入逻辑的DBSet。基于这个特定用法(美国用户示例),我们调查表达式树并换出(在运行时)dbo.Users的正常使用,为我们注入&#39; join&#39;的新IQueryable使用一个ExpressionVisitor

基本上,由于我们使用流畅的API定义了连接一次,并且我们知道了用法(IQueryable.Expression调查),我们可以注入连接&即时&#39;在个案基础上。

我遗漏了大约900条其他技术细节......