Entity Framework — свойство Entity только для чтения, сопоставленное со столбцом связанной таблицы

У меня есть интересная проблема, которую нужно решить, но, хотя она и распространена, похоже, что ее нелегко решить с помощью Entity Framework. Есть две таблицы:

Player(Id,TeamId,FirstName,LastName)
Team(Id, Name, IsProfessional)

Игрок может принадлежать только к одной команде. Используя TPT (сначала DB), у нас есть два класса, сопоставленные с этими таблицами:

public class Player
{
   public int Id{get;set;}
   public int TeamId{get;set;}
   public string FirstName{get; set;}
   public string LastName{get; set;}
   public Team Team{get;set;}
}

public class Team
{ 
   public int Id{get; set;}
   public string Name{get;set;}
   public bool IsProfessional{get;set;}
   public IEnumerable<Player> Players{get;}
}

Чего я хотел бы добиться, так это свойства IsProfessional для объекта Player:

public class Player
    {
       public int Id{get;set;}
       public int TeamId{get;set;}
       public string FirstName{get; set;}
       public string LastName{get; set;}
       public Team Team{get;set;}
       **public bool IsProfessional{get;}** should be read-only
    }

Можно ли настроить сопоставление таким образом, чтобы свойство IsProfessional можно было использовать в запросах linq?

var result= db.Players.Where(p=>p.IsProfessional==true);

и чтобы это поле заполнялось каждый раз, когда сущность Player материализуется?

Player pl = db.Players.Where(p=>p.FirstName="Lionel").FirstOrDefault();
if(pl.IsProfessional)
{
//do something...
}

Уже пробовал с:

  • Разделение объектов. Невозможно, потому что я хочу сохранить сопоставление команды и потому что отношение не 1: 1)
  • Сопоставление объекта Player с представлением базы данных. Мне это не понравилось, потому что у объекта Player есть другие отношения, которые мне нужны. Я знаю, что их можно создать вручную, но обновление edmx из базы данных приведет к сбросу ssdl.

Спасибо

Решение

Основываясь на втором варианте ответа Герта Арнольда, решение, которое соответствует моим потребностям, выглядит следующим образом:

  1. Создаю функцию GetIsProfessional (пришлось это сделать, т.к. вычисляемые поля обычно можно сделать только из собственных полей таблицы)

    CREATE FUNCTION [dbo].[GetIsProfessional](@teamId as INT)
    RETURNS bit
    
    BEGIN
    
    DECLARE @isProfi AS bit
    
    SELECT @isProfi = IsProfessional
    FROM Teams
    WHERE Id = @teamId
    
    RETURN @isProfi
    
    END
    
  2. Я создал вычисляемое поле в таблице Player

    ALTER TABLE Players ADD [IsProfessional] AS dbo.GetIsProfessional(TeamId)
    
  3. Поскольку я использую подход db first, я просто обновляю модель из базы данных, и все, я могу запросить это поле, и оно предварительно заполняется при материализации объекта Player.


person Misha N.    schedule 09.10.2012    source источник
comment
Итак, Player.IsProfessional должен давать тот же результат, что и Player.Team.IsProfessional? EF в настоящее время не поддерживает свойства, которые не сопоставляются с простым полем базы данных, извините, но, возможно, кто-то ответит с хорошей альтернативой.   -  person    schedule 09.10.2012
comment
Это именно то, что мне нужно, за исключением того, что Player.IsProfessional должен быть доступен только для чтения. Все альтернативы, которые я мог найти, не очень хороши.   -  person Misha N.    schedule 09.10.2012


Ответы (3)


Это невозможно сделать с EF. Есть несколько вариантов, которые не делают именно то, что вы хотите, но более или менее приближаются:

  1. Создайте свойство TeamPlayers в вашем контексте, которое возвращает игроков с включенной командой, чтобы вы всегда могли сделать player.Team.IsProfessional, даже если контекст уже был удален.

    public IQueryable<Player> TeamPlayers
    {
        get { return this.Players.Include("Team"); }
    }
    
  2. Создайте вычисляемое поле в таблице базы данных и сопоставьте его с помощью DatabaseGeneratedOption.Computed.

  3. Создайте статическое свойство в Player, которое возвращает выражение, обращающееся к Team.IsProfessional (требуется включенный живой контекст или команда):

    public static Expression<Func<Player, bool>> IsProfessional
    {
        get { return p => p.Team.IsProfessional; }
    }
    ...
    db.Players.Where( p=> p.FirstName="Lionel").Where(Player.IsProfessional)....
    

Я бы предпочел вычисляемое поле, потому что оно всегда заполнено, поэтому вы можете использовать его внутри и вне контекста.

person Gert Arnold    schedule 10.10.2012
comment
Здорово! Вычисляемое поле лучше всего соответствует моим потребностям: 1) я могу запросить вычисляемое поле, 2) оно заполнено другими свойствами проигрывателя. Большое спасибо! - person Misha N.; 10.10.2012

Вы можете использовать System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute< /em>, чтобы предотвратить сопоставление свойства IsProfessional следующим образом:

// Mapped part of entity
public class Player
{
    public int Id { get; set; }
    public int TeamId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Team Team { get; set; }
}

// Unmapped part of entity
using System.ComponentModel.DataAnnotations.Schema;
...
public partial class Player
{
    [NotMapped()]
    public bool IsProfessional { get { /* ... IsProfessional calculation logic comes here ... */ } }
}

Я использовал этот атрибут в подходе EF5's Model First и запросил DbContext/DbSet и ObjectContext/ObjectQuery без каких-либо исключений. (100% проверено)

person Perseus    schedule 05.06.2013
comment
Хорошо, но это свойство не отображается в источнике данных, который я создаю из этого объекта. - person Alin I; 07.04.2014
comment
Обычно я использую ObjectDataSource, и он показывает все общедоступные свойства моих классов сущностей. Я думаю, что EntityDataSource просто прочитал сопоставленные свойства сгенерированных сущностей из-за их объявления схемы в файле .edmx. - person Perseus; 10.04.2014
comment
Это работает? Как насчет проблем Linq, указанных выше? - person Worthy7; 15.02.2017
comment
Достаточно предоставить вычисляемый (виртуальный) столбец данных вместе с подходом Eager Loading, но нет способа перевести его логику в столбец (столбцы) реальных данных в предложении WHERE пользовательского запроса. - person Perseus; 16.02.2017

Что, если вы расширите Player, чтобы иметь свойство, которое извлекается из Team?

public partial class Player
{
   public int Id{get;set;}
   public int TeamId{get;set;}
   public string FirstName{get; set;}
   public string LastName{get; set;}
   public Team Team{get;set;}

   public bool IsProfessional{ get { return Team.IsProfessional; } }
}

Конечно, если вы беспокоитесь о регенерации EDMX, вы можете сделать его частичным:

public partial class Player
{
   public bool IsProfessional{ get { return Team.IsProfessional; } }
}
person kevin_fitz    schedule 09.10.2012
comment
Да, но таким образом Player.IsProfessional нельзя использовать в запросах linq (как я объяснил в вопросе). Кроме того, при доступе к свойству Player.IsProfessional он будет выполнять sql-запрос только для получения одного логического значения из базы данных. Я бы предпочел, чтобы это логическое значение заполнялось одновременно со всеми другими свойствами Player. - person Misha N.; 10.10.2012