Entity Framework 4 w/ MVC 2 - 解决 EF4 无法在表单字段中允许空字符串的问题

发布于 2024-10-12 15:33:08 字数 10186 浏览 0 评论 0原文

最终编辑:成功!有一个数据注释规则可以阻止将空字符串设置为 null。我能够在这里使用该解决方案(MVC2 实体框架 4 中所需字符串属性的服务器端验证不起作用)。现在就像一个魅力。我将保留帖子的其余部分,以防任何人都可以从中学习。

决定重写这篇文章以提供我能提供的所有详细信息。帖子很长,给出了所有代码,所以请耐心等待。

我正在使用 EF4 作为基础编写一个 MVC 2 项目。我的所有数据库列都是不可为空的。

正如我之前所说,当我测试空表单的情况时,我遇到了 EF4 抛出 ConstraintException 的问题。异常是从 Designer.cs 文件中出现的,特别是在如下代码中:

    [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
    [DataMemberAttribute()]
    public global::System.String GameTitle
    {
        get
        {
            return _GameTitle;
        }
        set
        {
            OnGameTitleChanging(value);
            ReportPropertyChanging("GameTitle");
            _GameTitle = StructuralObject.SetValidValue(value, false); // <-- this is where the exception is being thrown
            ReportPropertyChanged("GameTitle");
            OnGameTitleChanged();
        }
    }
    private global::System.String _GameTitle;
    partial void OnGameTitleChanging(global::System.String value);
    partial void OnGameTitleChanged();

异常未被 MVC 捕获和处理。相反,我不断收到未捕获的异常和 YSoD。我的控制器(请原谅混乱 - 我试图让存储库级别验证工作,见下文):

    [HttpPost]
    public ActionResult CreateReview([Bind(Prefix = "GameData")]Game newGame, int[] PlatformIDs)
    {
       /* try
        {
            _gameRepository.ValidateGame(newGame, PlatformIDs);
        }
        catch (RulesException ex)
        {
            ex.CopyTo(ModelState);
        }

        if (ModelState.IsValid)
        {
            return RedirectToAction("Index");
        }
        else
        {
            var genres = _siteDB.Genres.OrderBy(g => g.Name).ToList();
            var platforms = _siteDB.Platforms.OrderBy(p => p.PlatformID).ToList();

            List<PlatformListing> platformListings = new List<PlatformListing>();

            foreach(Platform platform in platforms)
            {
                platformListings.Add(new PlatformListing { Platform = platform, IsSelected = false });
            }

            var model = new AdminGameViewModel { GameData = newGame, AllGenres = genres, AllPlatforms = platformListings };
            return View(model);
        }*/

        if (PlatformIDs == null || PlatformIDs.Length == 0)
        {
            ModelState.AddModelError("PlatformIDs", "You need to select at least one platform for the game");
        }

        try
        {
            foreach(var p in PlatformIDs)
            {
                Platform plat = _siteDB.Platforms.Single(pl => p == pl.PlatformID);
                newGame.Platforms.Add(plat);
            }

            newGame.LastModified = DateTime.Now;

            if (ModelState.IsValid)
            {
                _siteDB.Games.AddObject(newGame);
                _siteDB.SaveChanges();

                return RedirectToAction("Index");
            }
            else
            {
                return View();
            }
        }
        catch
        {
            return View();
        }
    }

我已经尝试了几种方法来让验证工作。第一个是 Steven Sanderson 的“让模型处理它”,如他的 aPress MVC2 书中所述。我认为尝试验证的最佳位置是在存储库中,因为无论如何数据都必须传递到那里,而且我想让我的控制器保持精简。代码库:

public class HGGameRepository : IGameRepository
{
    private HGEntities _siteDB = new HGEntities();

    public List<Game> Games
    {
        get { return _siteDB.Games.ToList(); }
    }

    public void ValidateGame(Game game, int[] PlatformIDs)
    {
        var errors = new RulesException<Game>();

        if (string.IsNullOrEmpty(game.GameTitle))
        {
            errors.ErrorFor(x => x.GameTitle, "A game must have a title");
        }

        if (string.IsNullOrEmpty(game.ReviewText))
        {
            errors.ErrorFor(x => x.ReviewText, "A review must be written");
        }

        if (game.ReviewScore <= 0 || game.ReviewScore > 5)
        {
            errors.ErrorFor(x => x.ReviewScore, "A game must have a review score, and the score must be between 1 and 5");
        }

        if (string.IsNullOrEmpty(game.Pros))
        {
            errors.ErrorFor(x => x.Pros, "Each game review must have a list of pros");
        }

        if (string.IsNullOrEmpty(game.Cons))
        {
            errors.ErrorFor(x => x.Cons, "Each game review must have a list of cons");
        }

        if (PlatformIDs == null || PlatformIDs.Length == 0)
        {
            errors.ErrorForModel("A game must belong to at least one platform");
        }

        if (game.Genre.Equals(null)  || game.GenreID == 0)
        {
            errors.ErrorFor(x => x.Genre, "A game must be associated with a genre");
        }

        if (errors.Errors.Any())
        {
            throw errors;
        }
        else
        {
            game.Platforms.Clear(); // see if there's a more elegant way to remove changed platforms

            foreach (int id in PlatformIDs)
            {
                Platform plat = _siteDB.Platforms.Single(pl => pl.PlatformID == id);
                game.Platforms.Add(plat);
            }

            SaveGame(game);
        }
    }

    public void SaveGame(Game game)
    {
        if (game.GameID == 0)
        {
            _siteDB.Games.AddObject(game);
        }

        game.LastModified = DateTime.Now;
        _siteDB.SaveChanges();
    }

    public Game GetGame(int id)
    {
        return _siteDB.Games.Include("Genre").Include("Platforms").SingleOrDefault(g => g.GameID == id);
    }

    public IEnumerable<Game> GetGame(string title)
    {
        return _siteDB.Games.Include("Genre").Include("Platforms").Where(g => g.GameTitle.StartsWith(title)).AsEnumerable<Game>();
    }

    public List<Game> GetGamesByGenre(int id)
    {
        return _siteDB.Games.Where(g => g.GenreID == id).ToList();
    }

    public List<Game> GetGamesByGenre(string genre)
    {
        return _siteDB.Games.Where(g => g.Genre.Name == genre).ToList();
    }

    public List<Game> GetGamesByPlatform(int id)
    {
        return _siteDB.Games.Where(g => g.Platforms.Any(p => p.PlatformID == id)).ToList();
    }

    public List<Game> GetGamesByPlatform(string platform)
    {
        return _siteDB.Games.Where(g => g.Platforms.Any(p => p.Name == platform)).ToList();
    }
}

}

RulesException/Rule Violation 类(取自他的书):

public class RuleViolation
{
    public LambdaExpression Property { get; set; }
    public string Message { get; set; }
}

public class RulesException : Exception
{
    public readonly IList<RuleViolation> Errors = new List<RuleViolation>();
    private readonly static Expression<Func<object, object>> thisObject = x => x;

    public void ErrorForModel(string message)
    {
        Errors.Add(new RuleViolation { Property = thisObject, Message = message });
    }
}

public class RulesException<TModel> : RulesException
{
    public void ErrorFor<TProperty>(Expression<Func<TModel, TProperty>> property, string message)
    {
        Errors.Add(new RuleViolation { Property = property, Message = message });
    }
}

这不起作用,所以我决定尝试 Chris Sells 的使用 IDataErrorInfo 的方法(如下所示:http://sellsbrothers.com/Posts/Details/12700)。我的代码:

public partial class Game : IDataErrorInfo
{
    public string Error
    {
        get
        {
            if (Platforms.Count == 0)
            {
                return "A game must be associated with at least one platform";
            }

            return null;
        }
    }

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "GameTitle":
                    if (string.IsNullOrEmpty(GameTitle))
                    {
                        return "Game must have a title";
                    }
                    break;

                case "ReviewText":
                    if (string.IsNullOrEmpty(ReviewText))
                    {
                        return "Game must have an associated review";
                    }
                    break;

                case "Pros":
                    if (string.IsNullOrEmpty(Pros))
                    {
                        return "Game must have a list of pros";
                    }
                    break;

                case "Cons":
                    if (string.IsNullOrEmpty(Cons))
                    {
                        return "Game must have a list of cons";
                    }
                    break;
            }

            return null;
        }
    }
}

}

再次,它不起作用。

尝试简单的数据注释:

[MetadataType(typeof(GameValidation))]
public partial class Game
{
    class GameValidation
    {
        [Required(ErrorMessage="A game must have a title")]
        public string GameTitle { get; set; }

        [Required(ErrorMessage = "A game must have an associated review")]
        public string ReviewText { get; set; }

        [Required(ErrorMessage="A game must have a list of pros associated with it")]
        public string Pros { get; set; }

        [Required(ErrorMessage="A game must have a set of cons associated with it")]
        public string Cons { get; set; }
    }
}

也不起作用。

所有这些的共同点是 EF 设计者抛出异常,并且该异常未被 MVC 捕获。我完全不知所措。

编辑:从这里交叉发布: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/643e0267-fc7c-44c4-99da-ced643a736bf

与其他人有同样的问题:http://social.msdn.microsoft.com/论坛/en-US/adodotnetentityframework/thread/4fee653a-4c8c-4a40-b3cf-4944923a8c8d/

FINAL EDIT: Success! There's a data annotation rule that stops empty strings from being set to null. I was able to use the solution here (Server-side validation of a REQUIRED String Property in MVC2 Entity Framework 4 does not work). Works like a charm now. I'll keep the rest of my post as-is in case anyone can learn from it.

Decided to rewrite this post to give all the detail I can. Long post, given all the code, so please bear with me.

I'm writing an MVC 2 project using EF4 as a base. All of my db columns are non-nullable.

Like I said before, I'm having issues with EF4 throwing a ConstraintException when I test the case of an empty form. The exception is springing up from the Designer.cs file, specifically in code like:

    [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
    [DataMemberAttribute()]
    public global::System.String GameTitle
    {
        get
        {
            return _GameTitle;
        }
        set
        {
            OnGameTitleChanging(value);
            ReportPropertyChanging("GameTitle");
            _GameTitle = StructuralObject.SetValidValue(value, false); // <-- this is where the exception is being thrown
            ReportPropertyChanged("GameTitle");
            OnGameTitleChanged();
        }
    }
    private global::System.String _GameTitle;
    partial void OnGameTitleChanging(global::System.String value);
    partial void OnGameTitleChanged();

The exception IS NOT being caught and handled by MVC. Instead, I keep getting an uncaught exception and a YSoD. My controller (excuse the mess - I was trying to get repository level validation to work, see below):

    [HttpPost]
    public ActionResult CreateReview([Bind(Prefix = "GameData")]Game newGame, int[] PlatformIDs)
    {
       /* try
        {
            _gameRepository.ValidateGame(newGame, PlatformIDs);
        }
        catch (RulesException ex)
        {
            ex.CopyTo(ModelState);
        }

        if (ModelState.IsValid)
        {
            return RedirectToAction("Index");
        }
        else
        {
            var genres = _siteDB.Genres.OrderBy(g => g.Name).ToList();
            var platforms = _siteDB.Platforms.OrderBy(p => p.PlatformID).ToList();

            List<PlatformListing> platformListings = new List<PlatformListing>();

            foreach(Platform platform in platforms)
            {
                platformListings.Add(new PlatformListing { Platform = platform, IsSelected = false });
            }

            var model = new AdminGameViewModel { GameData = newGame, AllGenres = genres, AllPlatforms = platformListings };
            return View(model);
        }*/

        if (PlatformIDs == null || PlatformIDs.Length == 0)
        {
            ModelState.AddModelError("PlatformIDs", "You need to select at least one platform for the game");
        }

        try
        {
            foreach(var p in PlatformIDs)
            {
                Platform plat = _siteDB.Platforms.Single(pl => p == pl.PlatformID);
                newGame.Platforms.Add(plat);
            }

            newGame.LastModified = DateTime.Now;

            if (ModelState.IsValid)
            {
                _siteDB.Games.AddObject(newGame);
                _siteDB.SaveChanges();

                return RedirectToAction("Index");
            }
            else
            {
                return View();
            }
        }
        catch
        {
            return View();
        }
    }

I've tried several things to get validation to work. The first was Steven Sanderson's "Let the model handle it" as described in his aPress MVC2 book. I figured the best place to attempt validation would be in the repository, since the data would have to be passed in there anyway, and I'd like to keep my controller thin. The repo:

public class HGGameRepository : IGameRepository
{
    private HGEntities _siteDB = new HGEntities();

    public List<Game> Games
    {
        get { return _siteDB.Games.ToList(); }
    }

    public void ValidateGame(Game game, int[] PlatformIDs)
    {
        var errors = new RulesException<Game>();

        if (string.IsNullOrEmpty(game.GameTitle))
        {
            errors.ErrorFor(x => x.GameTitle, "A game must have a title");
        }

        if (string.IsNullOrEmpty(game.ReviewText))
        {
            errors.ErrorFor(x => x.ReviewText, "A review must be written");
        }

        if (game.ReviewScore <= 0 || game.ReviewScore > 5)
        {
            errors.ErrorFor(x => x.ReviewScore, "A game must have a review score, and the score must be between 1 and 5");
        }

        if (string.IsNullOrEmpty(game.Pros))
        {
            errors.ErrorFor(x => x.Pros, "Each game review must have a list of pros");
        }

        if (string.IsNullOrEmpty(game.Cons))
        {
            errors.ErrorFor(x => x.Cons, "Each game review must have a list of cons");
        }

        if (PlatformIDs == null || PlatformIDs.Length == 0)
        {
            errors.ErrorForModel("A game must belong to at least one platform");
        }

        if (game.Genre.Equals(null)  || game.GenreID == 0)
        {
            errors.ErrorFor(x => x.Genre, "A game must be associated with a genre");
        }

        if (errors.Errors.Any())
        {
            throw errors;
        }
        else
        {
            game.Platforms.Clear(); // see if there's a more elegant way to remove changed platforms

            foreach (int id in PlatformIDs)
            {
                Platform plat = _siteDB.Platforms.Single(pl => pl.PlatformID == id);
                game.Platforms.Add(plat);
            }

            SaveGame(game);
        }
    }

    public void SaveGame(Game game)
    {
        if (game.GameID == 0)
        {
            _siteDB.Games.AddObject(game);
        }

        game.LastModified = DateTime.Now;
        _siteDB.SaveChanges();
    }

    public Game GetGame(int id)
    {
        return _siteDB.Games.Include("Genre").Include("Platforms").SingleOrDefault(g => g.GameID == id);
    }

    public IEnumerable<Game> GetGame(string title)
    {
        return _siteDB.Games.Include("Genre").Include("Platforms").Where(g => g.GameTitle.StartsWith(title)).AsEnumerable<Game>();
    }

    public List<Game> GetGamesByGenre(int id)
    {
        return _siteDB.Games.Where(g => g.GenreID == id).ToList();
    }

    public List<Game> GetGamesByGenre(string genre)
    {
        return _siteDB.Games.Where(g => g.Genre.Name == genre).ToList();
    }

    public List<Game> GetGamesByPlatform(int id)
    {
        return _siteDB.Games.Where(g => g.Platforms.Any(p => p.PlatformID == id)).ToList();
    }

    public List<Game> GetGamesByPlatform(string platform)
    {
        return _siteDB.Games.Where(g => g.Platforms.Any(p => p.Name == platform)).ToList();
    }
}

}

The RulesException/Rule Violation classes (as taken from his book):

public class RuleViolation
{
    public LambdaExpression Property { get; set; }
    public string Message { get; set; }
}

public class RulesException : Exception
{
    public readonly IList<RuleViolation> Errors = new List<RuleViolation>();
    private readonly static Expression<Func<object, object>> thisObject = x => x;

    public void ErrorForModel(string message)
    {
        Errors.Add(new RuleViolation { Property = thisObject, Message = message });
    }
}

public class RulesException<TModel> : RulesException
{
    public void ErrorFor<TProperty>(Expression<Func<TModel, TProperty>> property, string message)
    {
        Errors.Add(new RuleViolation { Property = property, Message = message });
    }
}

That did not work, so I decided to try Chris Sells' method of using IDataErrorInfo (as seen here: http://sellsbrothers.com/Posts/Details/12700). My code:

public partial class Game : IDataErrorInfo
{
    public string Error
    {
        get
        {
            if (Platforms.Count == 0)
            {
                return "A game must be associated with at least one platform";
            }

            return null;
        }
    }

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "GameTitle":
                    if (string.IsNullOrEmpty(GameTitle))
                    {
                        return "Game must have a title";
                    }
                    break;

                case "ReviewText":
                    if (string.IsNullOrEmpty(ReviewText))
                    {
                        return "Game must have an associated review";
                    }
                    break;

                case "Pros":
                    if (string.IsNullOrEmpty(Pros))
                    {
                        return "Game must have a list of pros";
                    }
                    break;

                case "Cons":
                    if (string.IsNullOrEmpty(Cons))
                    {
                        return "Game must have a list of cons";
                    }
                    break;
            }

            return null;
        }
    }
}

}

Again, it did not work.

Trying simple data annotations:

[MetadataType(typeof(GameValidation))]
public partial class Game
{
    class GameValidation
    {
        [Required(ErrorMessage="A game must have a title")]
        public string GameTitle { get; set; }

        [Required(ErrorMessage = "A game must have an associated review")]
        public string ReviewText { get; set; }

        [Required(ErrorMessage="A game must have a list of pros associated with it")]
        public string Pros { get; set; }

        [Required(ErrorMessage="A game must have a set of cons associated with it")]
        public string Cons { get; set; }
    }
}

Also did not work.

The common denominator with all of this is the EF designer throwing an exception, and the exception not being caught by MVC. I'm at a complete loss.

EDIT: Cross-posted from here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/643e0267-fc7c-44c4-99da-ced643a736bf

Same issue with someone else: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/4fee653a-4c8c-4a40-b3cf-4944923a8c8d/

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夏末染殇 2024-10-19 15:33:08

就我个人而言,我认为将其存储在数据库中没有意义:

Id   Description
1    Foo
2    ''
3    Bar

对我来说似乎很愚蠢。

您可以将其设为可为空,而不是存储空白字段。

如果您不同意,那么您可以在数据库中设置默认值,并将StoreGeneratePattern设置为已计算

或者您可以为实体设置一个角色,用于设置值。

Personally i don't see a point in storing this in the database:

Id   Description
1    Foo
2    ''
3    Bar

Seems silly to me.

Instead of storing blank fields you make it nullable.

If you disagree, then you could set a default value in the database, and set the StoreGeneratedPattern to Computed.

Or you could have a ctor for the entity, which sets the value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文